stl-rb 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/ext/stl/ext.cpp +29 -25
- data/ext/stl/extconf.rb +1 -1
- data/ext/stl/stl.hpp +502 -426
- data/lib/stl/version.rb +1 -1
- data/lib/stl.rb +13 -13
- metadata +3 -3
data/ext/stl/stl.hpp
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
* STL C++ v0.
|
|
1
|
+
/*
|
|
2
|
+
* STL C++ v0.3.0
|
|
3
3
|
* https://github.com/ankane/stl-cpp
|
|
4
4
|
* Unlicense OR MIT License
|
|
5
5
|
*
|
|
@@ -18,80 +18,104 @@
|
|
|
18
18
|
|
|
19
19
|
#include <algorithm>
|
|
20
20
|
#include <cmath>
|
|
21
|
+
#include <cstddef>
|
|
21
22
|
#include <numeric>
|
|
22
23
|
#include <optional>
|
|
24
|
+
#include <span>
|
|
23
25
|
#include <stdexcept>
|
|
24
26
|
#include <tuple>
|
|
27
|
+
#include <utility>
|
|
25
28
|
#include <vector>
|
|
26
29
|
|
|
27
|
-
#if __cplusplus >= 202002L
|
|
28
|
-
#include <span>
|
|
29
|
-
#endif
|
|
30
|
-
|
|
31
30
|
namespace stl {
|
|
32
31
|
|
|
33
|
-
namespace {
|
|
32
|
+
namespace detail {
|
|
34
33
|
|
|
34
|
+
// TODO use span.at() for C++26
|
|
35
35
|
template<typename T>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
T& span_at(std::span<T> sp, size_t pos) {
|
|
37
|
+
if (pos >= sp.size()) [[unlikely]] {
|
|
38
|
+
throw std::out_of_range("pos >= size()");
|
|
39
|
+
}
|
|
40
|
+
return sp[pos];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
template<typename T>
|
|
44
|
+
bool est(
|
|
45
|
+
const std::vector<T>& y,
|
|
46
|
+
size_t n,
|
|
47
|
+
size_t len,
|
|
48
|
+
int ideg,
|
|
49
|
+
T xs,
|
|
50
|
+
T& ys,
|
|
51
|
+
size_t nleft,
|
|
52
|
+
size_t nright,
|
|
53
|
+
std::vector<T>& w,
|
|
54
|
+
bool userw,
|
|
55
|
+
const std::vector<T>& rw
|
|
56
|
+
) {
|
|
57
|
+
T range = static_cast<T>(n) - static_cast<T>(1.0);
|
|
58
|
+
T h = std::max(xs - static_cast<T>(nleft), static_cast<T>(nright) - xs);
|
|
39
59
|
|
|
40
60
|
if (len > n) {
|
|
41
|
-
h +=
|
|
61
|
+
h += static_cast<T>((len - n) / 2);
|
|
42
62
|
}
|
|
43
63
|
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
T h9 = static_cast<T>(0.999) * h;
|
|
65
|
+
T h1 = static_cast<T>(0.001) * h;
|
|
46
66
|
|
|
47
67
|
// compute weights
|
|
48
|
-
|
|
49
|
-
for (
|
|
50
|
-
w
|
|
51
|
-
|
|
68
|
+
T a = 0.0;
|
|
69
|
+
for (size_t j = nleft; j <= nright; j++) {
|
|
70
|
+
w.at(j - 1) = 0.0;
|
|
71
|
+
T r = std::abs(static_cast<T>(j) - xs);
|
|
52
72
|
if (r <= h9) {
|
|
53
73
|
if (r <= h1) {
|
|
54
|
-
w
|
|
74
|
+
w.at(j - 1) = 1.0;
|
|
55
75
|
} else {
|
|
56
|
-
w
|
|
76
|
+
w.at(j - 1) = static_cast<T>(std::pow(1.0 - std::pow(r / h, 3.0), 3.0));
|
|
57
77
|
}
|
|
58
78
|
if (userw) {
|
|
59
|
-
w
|
|
79
|
+
w.at(j - 1) *= rw.at(j - 1);
|
|
60
80
|
}
|
|
61
|
-
a += w
|
|
81
|
+
a += w.at(j - 1);
|
|
62
82
|
}
|
|
63
83
|
}
|
|
64
84
|
|
|
65
85
|
if (a <= 0.0) {
|
|
66
86
|
return false;
|
|
67
|
-
} else {
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
} else {
|
|
88
|
+
// weighted least squares
|
|
89
|
+
for (size_t j = nleft; j <= nright; j++) {
|
|
90
|
+
// make sum of w(j) == 1
|
|
91
|
+
w.at(j - 1) /= a;
|
|
70
92
|
}
|
|
71
93
|
|
|
72
|
-
if (h > 0.0 && ideg > 0) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
94
|
+
if (h > 0.0 && ideg > 0) {
|
|
95
|
+
// use linear fit
|
|
96
|
+
T a = 0.0;
|
|
97
|
+
for (size_t j = nleft; j <= nright; j++) {
|
|
98
|
+
// weighted center of x values
|
|
99
|
+
a += w.at(j - 1) * static_cast<T>(j);
|
|
76
100
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
for (
|
|
80
|
-
c += w
|
|
101
|
+
T b = xs - a;
|
|
102
|
+
T c = 0.0;
|
|
103
|
+
for (size_t j = nleft; j <= nright; j++) {
|
|
104
|
+
c += w.at(j - 1) * std::pow(static_cast<T>(j) - a, static_cast<T>(2.0));
|
|
81
105
|
}
|
|
82
106
|
if (std::sqrt(c) > 0.001 * range) {
|
|
83
107
|
b /= c;
|
|
84
108
|
|
|
85
109
|
// points are spread out enough to compute slope
|
|
86
|
-
for (
|
|
87
|
-
w
|
|
110
|
+
for (size_t j = nleft; j <= nright; j++) {
|
|
111
|
+
w.at(j - 1) *= b * (static_cast<T>(j) - a) + static_cast<T>(1.0);
|
|
88
112
|
}
|
|
89
113
|
}
|
|
90
114
|
}
|
|
91
115
|
|
|
92
|
-
|
|
93
|
-
for (
|
|
94
|
-
|
|
116
|
+
ys = 0.0;
|
|
117
|
+
for (size_t j = nleft; j <= nright; j++) {
|
|
118
|
+
ys += w.at(j - 1) * y.at(j - 1);
|
|
95
119
|
}
|
|
96
120
|
|
|
97
121
|
return true;
|
|
@@ -99,42 +123,62 @@ bool est(const T* y, size_t n, size_t len, int ideg, T xs, T* ys, size_t nleft,
|
|
|
99
123
|
}
|
|
100
124
|
|
|
101
125
|
template<typename T>
|
|
102
|
-
void ess(
|
|
126
|
+
void ess(
|
|
127
|
+
const std::vector<T>& y,
|
|
128
|
+
size_t n,
|
|
129
|
+
size_t len,
|
|
130
|
+
int ideg,
|
|
131
|
+
size_t njump,
|
|
132
|
+
bool userw,
|
|
133
|
+
const std::vector<T>& rw,
|
|
134
|
+
std::span<T> ys,
|
|
135
|
+
std::vector<T>& res
|
|
136
|
+
) {
|
|
103
137
|
if (n < 2) {
|
|
104
|
-
ys
|
|
138
|
+
span_at(ys, 0) = y.at(0);
|
|
105
139
|
return;
|
|
106
140
|
}
|
|
107
141
|
|
|
108
142
|
size_t nleft = 0;
|
|
109
143
|
size_t nright = 0;
|
|
110
144
|
|
|
111
|
-
|
|
145
|
+
size_t newnj = std::min(njump, n - 1);
|
|
112
146
|
if (len >= n) {
|
|
113
147
|
nleft = 1;
|
|
114
148
|
nright = n;
|
|
115
149
|
for (size_t i = 1; i <= n; i += newnj) {
|
|
116
|
-
|
|
150
|
+
bool ok = est(
|
|
151
|
+
y, n, len, ideg, static_cast<T>(i), span_at(ys, i - 1), nleft, nright, res, userw,
|
|
152
|
+
rw
|
|
153
|
+
);
|
|
117
154
|
if (!ok) {
|
|
118
|
-
ys
|
|
155
|
+
span_at(ys, i - 1) = y.at(i - 1);
|
|
119
156
|
}
|
|
120
157
|
}
|
|
121
|
-
} else if (newnj == 1) {
|
|
122
|
-
|
|
158
|
+
} else if (newnj == 1) {
|
|
159
|
+
// newnj equal to one, len less than n
|
|
160
|
+
size_t nsh = (len + 1) / 2;
|
|
123
161
|
nleft = 1;
|
|
124
162
|
nright = len;
|
|
125
|
-
for (size_t i = 1; i <= n; i++) {
|
|
163
|
+
for (size_t i = 1; i <= n; i++) {
|
|
164
|
+
// fitted value at i
|
|
126
165
|
if (i > nsh && nright != n) {
|
|
127
166
|
nleft += 1;
|
|
128
167
|
nright += 1;
|
|
129
168
|
}
|
|
130
|
-
|
|
169
|
+
bool ok = est(
|
|
170
|
+
y, n, len, ideg, static_cast<T>(i), span_at(ys, i - 1), nleft, nright, res, userw,
|
|
171
|
+
rw
|
|
172
|
+
);
|
|
131
173
|
if (!ok) {
|
|
132
|
-
ys
|
|
174
|
+
span_at(ys, i - 1) = y.at(i - 1);
|
|
133
175
|
}
|
|
134
176
|
}
|
|
135
|
-
} else {
|
|
136
|
-
|
|
137
|
-
|
|
177
|
+
} else {
|
|
178
|
+
// newnj greater than one, len less than n
|
|
179
|
+
size_t nsh = (len + 1) / 2;
|
|
180
|
+
for (size_t i = 1; i <= n; i += newnj) {
|
|
181
|
+
// fitted value at i
|
|
138
182
|
if (i < nsh) {
|
|
139
183
|
nleft = 1;
|
|
140
184
|
nright = len;
|
|
@@ -145,30 +189,36 @@ void ess(const T* y, size_t n, size_t len, int ideg, size_t njump, bool userw, c
|
|
|
145
189
|
nleft = i - nsh + 1;
|
|
146
190
|
nright = len + i - nsh;
|
|
147
191
|
}
|
|
148
|
-
|
|
192
|
+
bool ok = est(
|
|
193
|
+
y, n, len, ideg, static_cast<T>(i), span_at(ys, i - 1), nleft, nright, res, userw,
|
|
194
|
+
rw
|
|
195
|
+
);
|
|
149
196
|
if (!ok) {
|
|
150
|
-
ys
|
|
197
|
+
span_at(ys, i - 1) = y.at(i - 1);
|
|
151
198
|
}
|
|
152
199
|
}
|
|
153
200
|
}
|
|
154
201
|
|
|
155
202
|
if (newnj != 1) {
|
|
156
203
|
for (size_t i = 1; i <= n - newnj; i += newnj) {
|
|
157
|
-
|
|
158
|
-
for (
|
|
159
|
-
ys
|
|
204
|
+
T delta = (span_at(ys, i + newnj - 1) - span_at(ys, i - 1)) / static_cast<T>(newnj);
|
|
205
|
+
for (size_t j = i + 1; j <= i + newnj - 1; j++) {
|
|
206
|
+
span_at(ys, j - 1) = span_at(ys, i - 1) + delta * static_cast<T>(j - i);
|
|
160
207
|
}
|
|
161
208
|
}
|
|
162
|
-
|
|
209
|
+
size_t k = ((n - 1) / newnj) * newnj + 1;
|
|
163
210
|
if (k != n) {
|
|
164
|
-
|
|
211
|
+
bool ok = est(
|
|
212
|
+
y, n, len, ideg, static_cast<T>(n), span_at(ys, n - 1), nleft, nright, res, userw,
|
|
213
|
+
rw
|
|
214
|
+
);
|
|
165
215
|
if (!ok) {
|
|
166
|
-
ys
|
|
216
|
+
span_at(ys, n - 1) = y.at(n - 1);
|
|
167
217
|
}
|
|
168
218
|
if (k != n - 1) {
|
|
169
|
-
|
|
170
|
-
for (
|
|
171
|
-
ys
|
|
219
|
+
T delta = (span_at(ys, n - 1) - span_at(ys, k - 1)) / static_cast<T>(n - k);
|
|
220
|
+
for (size_t j = k + 1; j <= n - 1; j++) {
|
|
221
|
+
span_at(ys, j - 1) = span_at(ys, k - 1) + delta * static_cast<T>(j - k);
|
|
172
222
|
}
|
|
173
223
|
}
|
|
174
224
|
}
|
|
@@ -176,24 +226,24 @@ void ess(const T* y, size_t n, size_t len, int ideg, size_t njump, bool userw, c
|
|
|
176
226
|
}
|
|
177
227
|
|
|
178
228
|
template<typename T>
|
|
179
|
-
void ma(const T
|
|
180
|
-
|
|
181
|
-
|
|
229
|
+
void ma(const std::vector<T>& x, size_t n, size_t len, std::vector<T>& ave) {
|
|
230
|
+
size_t newn = n - len + 1;
|
|
231
|
+
auto flen = static_cast<double>(len);
|
|
182
232
|
double v = 0.0;
|
|
183
233
|
|
|
184
234
|
// get the first average
|
|
185
235
|
for (size_t i = 0; i < len; i++) {
|
|
186
|
-
v += x
|
|
236
|
+
v += x.at(i);
|
|
187
237
|
}
|
|
188
238
|
|
|
189
|
-
ave
|
|
239
|
+
ave.at(0) = static_cast<T>(v / flen);
|
|
190
240
|
if (newn > 1) {
|
|
191
241
|
size_t k = len;
|
|
192
242
|
size_t m = 0;
|
|
193
243
|
for (size_t j = 1; j < newn; j++) {
|
|
194
244
|
// window down the array
|
|
195
|
-
v = v - x
|
|
196
|
-
ave
|
|
245
|
+
v = v - x.at(m) + x.at(k);
|
|
246
|
+
ave.at(j) = static_cast<T>(v / flen);
|
|
197
247
|
k += 1;
|
|
198
248
|
m += 1;
|
|
199
249
|
}
|
|
@@ -201,165 +251,259 @@ void ma(const T* x, size_t n, size_t len, T* ave) {
|
|
|
201
251
|
}
|
|
202
252
|
|
|
203
253
|
template<typename T>
|
|
204
|
-
void fts(
|
|
254
|
+
void fts(
|
|
255
|
+
const std::vector<T>& x,
|
|
256
|
+
size_t n,
|
|
257
|
+
size_t np,
|
|
258
|
+
std::vector<T>& trend,
|
|
259
|
+
std::vector<T>& work
|
|
260
|
+
) {
|
|
205
261
|
ma(x, n, np, trend);
|
|
206
262
|
ma(trend, n - np + 1, np, work);
|
|
207
263
|
ma(work, n - 2 * np + 2, 3, trend);
|
|
208
264
|
}
|
|
209
265
|
|
|
210
266
|
template<typename T>
|
|
211
|
-
void rwts(const T
|
|
212
|
-
|
|
213
|
-
|
|
267
|
+
void rwts(std::span<const T> y, const std::vector<T>& fit, std::vector<T>& rw) {
|
|
268
|
+
// TODO use std::views::zip for C++23
|
|
269
|
+
for (size_t i = 0; i < y.size(); i++) {
|
|
270
|
+
rw.at(i) = std::abs(span_at(y, i) - fit.at(i));
|
|
214
271
|
}
|
|
215
272
|
|
|
216
|
-
|
|
217
|
-
|
|
273
|
+
size_t n = y.size();
|
|
274
|
+
size_t mid1 = (n - 1) / 2;
|
|
275
|
+
size_t mid2 = n / 2;
|
|
218
276
|
|
|
219
277
|
// sort
|
|
220
|
-
std::sort(rw
|
|
278
|
+
std::ranges::sort(rw);
|
|
221
279
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
280
|
+
T cmad = static_cast<T>(3.0) * (rw.at(mid1) + rw.at(mid2)); // 6 * median abs resid
|
|
281
|
+
T c9 = static_cast<T>(0.999) * cmad;
|
|
282
|
+
T c1 = static_cast<T>(0.001) * cmad;
|
|
225
283
|
|
|
226
|
-
|
|
227
|
-
|
|
284
|
+
// TODO use std::views::zip for C++23
|
|
285
|
+
for (size_t i = 0; i < y.size(); i++) {
|
|
286
|
+
T r = std::abs(span_at(y, i) - fit.at(i));
|
|
228
287
|
if (r <= c1) {
|
|
229
|
-
rw
|
|
288
|
+
rw.at(i) = 1.0;
|
|
230
289
|
} else if (r <= c9) {
|
|
231
|
-
rw
|
|
290
|
+
rw.at(i) = static_cast<T>(std::pow(1.0 - std::pow(r / cmad, 2.0), 2.0));
|
|
232
291
|
} else {
|
|
233
|
-
rw
|
|
292
|
+
rw.at(i) = 0.0;
|
|
234
293
|
}
|
|
235
294
|
}
|
|
236
295
|
}
|
|
237
296
|
|
|
238
297
|
template<typename T>
|
|
239
|
-
void ss(
|
|
298
|
+
void ss(
|
|
299
|
+
const std::vector<T>& y,
|
|
300
|
+
size_t n,
|
|
301
|
+
size_t np,
|
|
302
|
+
size_t ns,
|
|
303
|
+
int isdeg,
|
|
304
|
+
size_t nsjump,
|
|
305
|
+
bool userw,
|
|
306
|
+
std::vector<T>& rw,
|
|
307
|
+
std::vector<T>& season,
|
|
308
|
+
std::vector<T>& work1,
|
|
309
|
+
std::vector<T>& work2,
|
|
310
|
+
std::vector<T>& work3,
|
|
311
|
+
std::vector<T>& work4
|
|
312
|
+
) {
|
|
240
313
|
for (size_t j = 1; j <= np; j++) {
|
|
241
314
|
size_t k = (n - j) / np + 1;
|
|
242
315
|
|
|
243
316
|
for (size_t i = 1; i <= k; i++) {
|
|
244
|
-
work1
|
|
317
|
+
work1.at(i - 1) = y.at((i - 1) * np + j - 1);
|
|
245
318
|
}
|
|
246
319
|
if (userw) {
|
|
247
320
|
for (size_t i = 1; i <= k; i++) {
|
|
248
|
-
work3
|
|
321
|
+
work3.at(i - 1) = rw.at((i - 1) * np + j - 1);
|
|
249
322
|
}
|
|
250
323
|
}
|
|
251
|
-
ess(work1, k, ns, isdeg, nsjump, userw, work3, work2
|
|
324
|
+
ess(work1, k, ns, isdeg, nsjump, userw, work3, std::span{work2}.subspan(1), work4);
|
|
252
325
|
T xs = 0.0;
|
|
253
|
-
|
|
254
|
-
|
|
326
|
+
size_t nright = std::min(ns, k);
|
|
327
|
+
bool ok = est(work1, k, ns, isdeg, xs, work2.at(0), 1, nright, work4, userw, work3);
|
|
255
328
|
if (!ok) {
|
|
256
|
-
work2
|
|
329
|
+
work2.at(0) = work2.at(1);
|
|
257
330
|
}
|
|
258
|
-
xs = k + 1;
|
|
259
|
-
size_t nleft =
|
|
260
|
-
|
|
331
|
+
xs = static_cast<T>(k + 1);
|
|
332
|
+
size_t nleft = static_cast<size_t>(
|
|
333
|
+
std::max(1, static_cast<int>(k) - static_cast<int>(ns) + 1)
|
|
334
|
+
);
|
|
335
|
+
ok = est(work1, k, ns, isdeg, xs, work2.at(k + 1), nleft, k, work4, userw, work3);
|
|
261
336
|
if (!ok) {
|
|
262
|
-
work2
|
|
337
|
+
work2.at(k + 1) = work2.at(k);
|
|
263
338
|
}
|
|
264
339
|
for (size_t m = 1; m <= k + 2; m++) {
|
|
265
|
-
season
|
|
340
|
+
season.at((m - 1) * np + j - 1) = work2.at(m - 1);
|
|
266
341
|
}
|
|
267
342
|
}
|
|
268
343
|
}
|
|
269
344
|
|
|
270
345
|
template<typename T>
|
|
271
|
-
void onestp(
|
|
346
|
+
void onestp(
|
|
347
|
+
std::span<const T> y,
|
|
348
|
+
size_t np,
|
|
349
|
+
size_t ns,
|
|
350
|
+
size_t nt,
|
|
351
|
+
size_t nl,
|
|
352
|
+
int isdeg,
|
|
353
|
+
int itdeg,
|
|
354
|
+
int ildeg,
|
|
355
|
+
size_t nsjump,
|
|
356
|
+
size_t ntjump,
|
|
357
|
+
size_t nljump,
|
|
358
|
+
size_t ni,
|
|
359
|
+
bool userw,
|
|
360
|
+
std::vector<T>& rw,
|
|
361
|
+
std::vector<T>& season,
|
|
362
|
+
std::vector<T>& trend,
|
|
363
|
+
std::vector<T>& work1,
|
|
364
|
+
std::vector<T>& work2,
|
|
365
|
+
std::vector<T>& work3,
|
|
366
|
+
std::vector<T>& work4,
|
|
367
|
+
std::vector<T>& work5
|
|
368
|
+
) {
|
|
369
|
+
size_t n = y.size();
|
|
370
|
+
|
|
272
371
|
for (size_t j = 0; j < ni; j++) {
|
|
273
|
-
|
|
274
|
-
|
|
372
|
+
// TODO use std::views::zip for C++23
|
|
373
|
+
for (size_t i = 0; i < y.size(); i++) {
|
|
374
|
+
work1.at(i) = span_at(y, i) - trend.at(i);
|
|
275
375
|
}
|
|
276
376
|
|
|
277
377
|
ss(work1, n, np, ns, isdeg, nsjump, userw, rw, work2, work3, work4, work5, season);
|
|
278
378
|
fts(work2, n + 2 * np, np, work3, work1);
|
|
279
|
-
ess(work3, n, nl, ildeg, nljump, false, work4, work1, work5);
|
|
379
|
+
ess(work3, n, nl, ildeg, nljump, false, work4, std::span{work1}, work5);
|
|
380
|
+
// TODO use std::views::zip for C++23
|
|
280
381
|
for (size_t i = 0; i < n; i++) {
|
|
281
|
-
season
|
|
382
|
+
season.at(i) = work2.at(np + i) - work1.at(i);
|
|
282
383
|
}
|
|
283
|
-
|
|
284
|
-
|
|
384
|
+
// TODO use std::views::zip for C++23
|
|
385
|
+
for (size_t i = 0; i < y.size(); i++) {
|
|
386
|
+
work1.at(i) = span_at(y, i) - season.at(i);
|
|
285
387
|
}
|
|
286
|
-
ess(work1, n, nt, itdeg, ntjump, userw, rw, trend, work3);
|
|
388
|
+
ess(work1, n, nt, itdeg, ntjump, userw, rw, std::span{trend}, work3);
|
|
287
389
|
}
|
|
288
390
|
}
|
|
289
391
|
|
|
290
392
|
template<typename T>
|
|
291
|
-
void stl(
|
|
393
|
+
void stl(
|
|
394
|
+
std::span<const T> y,
|
|
395
|
+
size_t np,
|
|
396
|
+
size_t ns,
|
|
397
|
+
size_t nt,
|
|
398
|
+
size_t nl,
|
|
399
|
+
int isdeg,
|
|
400
|
+
int itdeg,
|
|
401
|
+
int ildeg,
|
|
402
|
+
size_t nsjump,
|
|
403
|
+
size_t ntjump,
|
|
404
|
+
size_t nljump,
|
|
405
|
+
size_t ni,
|
|
406
|
+
size_t no,
|
|
407
|
+
std::vector<T>& rw,
|
|
408
|
+
std::vector<T>& season,
|
|
409
|
+
std::vector<T>& trend
|
|
410
|
+
) {
|
|
411
|
+
size_t n = y.size();
|
|
412
|
+
|
|
292
413
|
if (ns < 3) {
|
|
293
|
-
throw std::invalid_argument
|
|
414
|
+
throw std::invalid_argument{"seasonal_length must be at least 3"};
|
|
294
415
|
}
|
|
295
416
|
if (nt < 3) {
|
|
296
|
-
throw std::invalid_argument
|
|
417
|
+
throw std::invalid_argument{"trend_length must be at least 3"};
|
|
297
418
|
}
|
|
298
419
|
if (nl < 3) {
|
|
299
|
-
throw std::invalid_argument
|
|
420
|
+
throw std::invalid_argument{"low_pass_length must be at least 3"};
|
|
300
421
|
}
|
|
301
422
|
if (np < 2) {
|
|
302
|
-
throw std::invalid_argument
|
|
423
|
+
throw std::invalid_argument{"period must be at least 2"};
|
|
303
424
|
}
|
|
304
425
|
|
|
305
426
|
if (isdeg != 0 && isdeg != 1) {
|
|
306
|
-
throw std::invalid_argument
|
|
427
|
+
throw std::invalid_argument{"seasonal_degree must be 0 or 1"};
|
|
307
428
|
}
|
|
308
429
|
if (itdeg != 0 && itdeg != 1) {
|
|
309
|
-
throw std::invalid_argument
|
|
430
|
+
throw std::invalid_argument{"trend_degree must be 0 or 1"};
|
|
310
431
|
}
|
|
311
432
|
if (ildeg != 0 && ildeg != 1) {
|
|
312
|
-
throw std::invalid_argument
|
|
433
|
+
throw std::invalid_argument{"low_pass_degree must be 0 or 1"};
|
|
313
434
|
}
|
|
314
435
|
|
|
315
436
|
if (ns % 2 != 1) {
|
|
316
|
-
throw std::invalid_argument
|
|
437
|
+
throw std::invalid_argument{"seasonal_length must be odd"};
|
|
317
438
|
}
|
|
318
439
|
if (nt % 2 != 1) {
|
|
319
|
-
throw std::invalid_argument
|
|
440
|
+
throw std::invalid_argument{"trend_length must be odd"};
|
|
320
441
|
}
|
|
321
442
|
if (nl % 2 != 1) {
|
|
322
|
-
throw std::invalid_argument
|
|
443
|
+
throw std::invalid_argument{"low_pass_length must be odd"};
|
|
323
444
|
}
|
|
324
445
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
446
|
+
std::vector<T> work1(n + 2 * np);
|
|
447
|
+
std::vector<T> work2(n + 2 * np);
|
|
448
|
+
std::vector<T> work3(n + 2 * np);
|
|
449
|
+
std::vector<T> work4(n + 2 * np);
|
|
450
|
+
std::vector<T> work5(n + 2 * np);
|
|
330
451
|
|
|
331
|
-
|
|
452
|
+
bool userw = false;
|
|
332
453
|
size_t k = 0;
|
|
333
454
|
|
|
334
455
|
while (true) {
|
|
335
|
-
onestp(
|
|
456
|
+
onestp(
|
|
457
|
+
y,
|
|
458
|
+
np,
|
|
459
|
+
ns,
|
|
460
|
+
nt,
|
|
461
|
+
nl,
|
|
462
|
+
isdeg,
|
|
463
|
+
itdeg,
|
|
464
|
+
ildeg,
|
|
465
|
+
nsjump,
|
|
466
|
+
ntjump,
|
|
467
|
+
nljump,
|
|
468
|
+
ni,
|
|
469
|
+
userw,
|
|
470
|
+
rw,
|
|
471
|
+
season,
|
|
472
|
+
trend,
|
|
473
|
+
work1,
|
|
474
|
+
work2,
|
|
475
|
+
work3,
|
|
476
|
+
work4,
|
|
477
|
+
work5
|
|
478
|
+
);
|
|
336
479
|
k += 1;
|
|
337
480
|
if (k > no) {
|
|
338
481
|
break;
|
|
339
482
|
}
|
|
340
483
|
for (size_t i = 0; i < n; i++) {
|
|
341
|
-
work1
|
|
484
|
+
work1.at(i) = trend.at(i) + season.at(i);
|
|
342
485
|
}
|
|
343
|
-
rwts(y,
|
|
486
|
+
rwts(y, work1, rw);
|
|
344
487
|
userw = true;
|
|
345
488
|
}
|
|
346
489
|
|
|
347
490
|
if (no <= 0) {
|
|
348
491
|
for (size_t i = 0; i < n; i++) {
|
|
349
|
-
rw
|
|
492
|
+
rw.at(i) = 1.0;
|
|
350
493
|
}
|
|
351
494
|
}
|
|
352
495
|
}
|
|
353
496
|
|
|
354
497
|
template<typename T>
|
|
355
498
|
double var(const std::vector<T>& series) {
|
|
356
|
-
|
|
499
|
+
double mean = std::accumulate(series.begin(), series.end(), 0.0)
|
|
500
|
+
/ static_cast<double>(series.size());
|
|
357
501
|
double sum = 0.0;
|
|
358
502
|
for (auto v : series) {
|
|
359
503
|
double diff = v - mean;
|
|
360
504
|
sum += diff * diff;
|
|
361
505
|
}
|
|
362
|
-
return sum / (series.size() - 1);
|
|
506
|
+
return sum / static_cast<double>(series.size() - 1);
|
|
363
507
|
}
|
|
364
508
|
|
|
365
509
|
template<typename T>
|
|
@@ -367,314 +511,256 @@ double strength(const std::vector<T>& component, const std::vector<T>& remainder
|
|
|
367
511
|
std::vector<T> sr;
|
|
368
512
|
sr.reserve(remainder.size());
|
|
369
513
|
for (size_t i = 0; i < remainder.size(); i++) {
|
|
370
|
-
sr.push_back(component
|
|
514
|
+
sr.push_back(component.at(i) + remainder.at(i));
|
|
371
515
|
}
|
|
372
516
|
return std::max(0.0, 1.0 - var(remainder) / var(sr));
|
|
373
517
|
}
|
|
374
518
|
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/// A STL result.
|
|
378
|
-
template<typename T = float>
|
|
379
|
-
class StlResult {
|
|
380
|
-
public:
|
|
381
|
-
/// Returns the seasonal component.
|
|
382
|
-
std::vector<T> seasonal;
|
|
383
|
-
|
|
384
|
-
/// Returns the trend component.
|
|
385
|
-
std::vector<T> trend;
|
|
386
|
-
|
|
387
|
-
/// Returns the remainder.
|
|
388
|
-
std::vector<T> remainder;
|
|
389
|
-
|
|
390
|
-
/// Returns the weights.
|
|
391
|
-
std::vector<T> weights;
|
|
392
|
-
|
|
393
|
-
/// Returns the seasonal strength.
|
|
394
|
-
inline double seasonal_strength() const {
|
|
395
|
-
return strength(seasonal, remainder);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/// Returns the trend strength.
|
|
399
|
-
inline double trend_strength() const {
|
|
400
|
-
return strength(trend, remainder);
|
|
401
|
-
}
|
|
402
|
-
};
|
|
519
|
+
} // namespace detail
|
|
403
520
|
|
|
404
521
|
/// A set of STL parameters.
|
|
405
|
-
|
|
406
|
-
public:
|
|
407
|
-
/// @private
|
|
408
|
-
std::optional<size_t> ns_ = std::nullopt;
|
|
409
|
-
private:
|
|
410
|
-
std::optional<size_t> nt_ = std::nullopt;
|
|
411
|
-
std::optional<size_t> nl_ = std::nullopt;
|
|
412
|
-
int isdeg_ = 0;
|
|
413
|
-
int itdeg_ = 1;
|
|
414
|
-
std::optional<int> ildeg_ = std::nullopt;
|
|
415
|
-
std::optional<size_t> nsjump_ = std::nullopt;
|
|
416
|
-
std::optional<size_t> ntjump_ = std::nullopt;
|
|
417
|
-
std::optional<size_t> nljump_ = std::nullopt;
|
|
418
|
-
std::optional<size_t> ni_ = std::nullopt;
|
|
419
|
-
std::optional<size_t> no_ = std::nullopt;
|
|
420
|
-
bool robust_ = false;
|
|
421
|
-
|
|
422
|
-
public:
|
|
522
|
+
struct StlParams {
|
|
423
523
|
/// Sets the length of the seasonal smoother.
|
|
424
|
-
|
|
425
|
-
this->ns_ = length;
|
|
426
|
-
return *this;
|
|
427
|
-
}
|
|
428
|
-
|
|
524
|
+
std::optional<size_t> seasonal_length = std::nullopt;
|
|
429
525
|
/// Sets the length of the trend smoother.
|
|
430
|
-
|
|
431
|
-
this->nt_ = length;
|
|
432
|
-
return *this;
|
|
433
|
-
}
|
|
434
|
-
|
|
526
|
+
std::optional<size_t> trend_length = std::nullopt;
|
|
435
527
|
/// Sets the length of the low-pass filter.
|
|
436
|
-
|
|
437
|
-
this->nl_ = length;
|
|
438
|
-
return *this;
|
|
439
|
-
}
|
|
440
|
-
|
|
528
|
+
std::optional<size_t> low_pass_length = std::nullopt;
|
|
441
529
|
/// Sets the degree of locally-fitted polynomial in seasonal smoothing.
|
|
442
|
-
|
|
443
|
-
this->isdeg_ = degree;
|
|
444
|
-
return *this;
|
|
445
|
-
}
|
|
446
|
-
|
|
530
|
+
int seasonal_degree = 0;
|
|
447
531
|
/// Sets the degree of locally-fitted polynomial in trend smoothing.
|
|
448
|
-
|
|
449
|
-
this->itdeg_ = degree;
|
|
450
|
-
return *this;
|
|
451
|
-
}
|
|
452
|
-
|
|
532
|
+
int trend_degree = 1;
|
|
453
533
|
/// Sets the degree of locally-fitted polynomial in low-pass smoothing.
|
|
454
|
-
|
|
455
|
-
this->ildeg_ = degree;
|
|
456
|
-
return *this;
|
|
457
|
-
}
|
|
458
|
-
|
|
534
|
+
std::optional<int> low_pass_degree = std::nullopt;
|
|
459
535
|
/// Sets the skipping value for seasonal smoothing.
|
|
460
|
-
|
|
461
|
-
this->nsjump_ = jump;
|
|
462
|
-
return *this;
|
|
463
|
-
}
|
|
464
|
-
|
|
536
|
+
std::optional<size_t> seasonal_jump = std::nullopt;
|
|
465
537
|
/// Sets the skipping value for trend smoothing.
|
|
466
|
-
|
|
467
|
-
this->ntjump_ = jump;
|
|
468
|
-
return *this;
|
|
469
|
-
}
|
|
470
|
-
|
|
538
|
+
std::optional<size_t> trend_jump = std::nullopt;
|
|
471
539
|
/// Sets the skipping value for low-pass smoothing.
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
540
|
+
std::optional<size_t> low_pass_jump = std::nullopt;
|
|
541
|
+
/// Sets the number of loops for updating the seasonal and trend components.
|
|
542
|
+
std::optional<size_t> inner_loops = std::nullopt;
|
|
543
|
+
/// Sets the number of iterations of robust fitting.
|
|
544
|
+
std::optional<size_t> outer_loops = std::nullopt;
|
|
545
|
+
/// Sets whether robustness iterations are to be used.
|
|
546
|
+
bool robust = false;
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
/// Seasonal-trend decomposition using Loess (STL).
|
|
550
|
+
template<typename T = float>
|
|
551
|
+
class Stl {
|
|
552
|
+
public:
|
|
553
|
+
/// Decomposes a time series from a vector.
|
|
554
|
+
Stl(const std::vector<T>& series, size_t period, const StlParams& params = StlParams());
|
|
555
|
+
|
|
556
|
+
/// Decomposes a time series from a span.
|
|
557
|
+
Stl(std::span<const T> series, size_t period, const StlParams& params = StlParams());
|
|
558
|
+
|
|
559
|
+
/// Returns the seasonal component.
|
|
560
|
+
const std::vector<T>& seasonal() const {
|
|
561
|
+
return seasonal_;
|
|
475
562
|
}
|
|
476
563
|
|
|
477
|
-
///
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
return *this;
|
|
564
|
+
/// Returns the trend component.
|
|
565
|
+
const std::vector<T>& trend() const {
|
|
566
|
+
return trend_;
|
|
481
567
|
}
|
|
482
568
|
|
|
483
|
-
///
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
return *this;
|
|
569
|
+
/// Returns the remainder.
|
|
570
|
+
const std::vector<T>& remainder() const {
|
|
571
|
+
return remainder_;
|
|
487
572
|
}
|
|
488
573
|
|
|
489
|
-
///
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
return *this;
|
|
574
|
+
/// Returns the weights.
|
|
575
|
+
const std::vector<T>& weights() const {
|
|
576
|
+
return weights_;
|
|
493
577
|
}
|
|
494
578
|
|
|
495
|
-
///
|
|
496
|
-
|
|
497
|
-
|
|
579
|
+
/// Returns the seasonal strength.
|
|
580
|
+
double seasonal_strength() const {
|
|
581
|
+
return detail::strength(seasonal_, remainder_);
|
|
582
|
+
}
|
|
498
583
|
|
|
499
|
-
///
|
|
500
|
-
|
|
501
|
-
|
|
584
|
+
/// Returns the trend strength.
|
|
585
|
+
double trend_strength() const {
|
|
586
|
+
return detail::strength(trend_, remainder_);
|
|
587
|
+
}
|
|
502
588
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
589
|
+
private:
|
|
590
|
+
std::vector<T> seasonal_;
|
|
591
|
+
std::vector<T> trend_;
|
|
592
|
+
std::vector<T> remainder_;
|
|
593
|
+
std::vector<T> weights_;
|
|
508
594
|
};
|
|
509
595
|
|
|
510
|
-
/// Creates a new set of STL parameters.
|
|
511
|
-
inline StlParams params() {
|
|
512
|
-
return StlParams();
|
|
513
|
-
}
|
|
514
|
-
|
|
515
596
|
template<typename T>
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
597
|
+
Stl<T>::Stl(std::span<const T> series, size_t period, const StlParams& params) {
|
|
598
|
+
std::span<const T> y = series;
|
|
599
|
+
size_t np = period;
|
|
600
|
+
size_t n = series.size();
|
|
520
601
|
|
|
521
|
-
if (n
|
|
522
|
-
throw std::invalid_argument
|
|
602
|
+
if (n / 2 < np) {
|
|
603
|
+
throw std::invalid_argument{"series has less than two periods"};
|
|
523
604
|
}
|
|
524
605
|
|
|
525
|
-
|
|
606
|
+
size_t ns = params.seasonal_length.value_or(np);
|
|
526
607
|
|
|
527
|
-
|
|
528
|
-
|
|
608
|
+
int isdeg = params.seasonal_degree;
|
|
609
|
+
int itdeg = params.trend_degree;
|
|
529
610
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
std::vector<T>(n)
|
|
535
|
-
};
|
|
611
|
+
std::vector<T> seasonal(n);
|
|
612
|
+
std::vector<T> trend(n);
|
|
613
|
+
std::vector<T> remainder;
|
|
614
|
+
std::vector<T> weights(n);
|
|
536
615
|
|
|
537
|
-
|
|
538
|
-
|
|
616
|
+
int ildeg = params.low_pass_degree.value_or(itdeg);
|
|
617
|
+
size_t newns = std::max(ns, static_cast<size_t>(3));
|
|
539
618
|
if (newns % 2 == 0) {
|
|
540
619
|
newns += 1;
|
|
541
620
|
}
|
|
542
621
|
|
|
543
|
-
|
|
544
|
-
auto nt =
|
|
545
|
-
|
|
546
|
-
|
|
622
|
+
size_t newnp = std::max(np, static_cast<size_t>(2));
|
|
623
|
+
auto nt = static_cast<size_t>(
|
|
624
|
+
std::ceil((1.5 * static_cast<float>(newnp)) / (1.0 - 1.5 / static_cast<float>(newns)))
|
|
625
|
+
);
|
|
626
|
+
nt = params.trend_length.value_or(nt);
|
|
627
|
+
nt = std::max(nt, static_cast<size_t>(3));
|
|
547
628
|
if (nt % 2 == 0) {
|
|
548
629
|
nt += 1;
|
|
549
630
|
}
|
|
550
631
|
|
|
551
|
-
|
|
552
|
-
if (nl % 2 == 0 && !
|
|
632
|
+
size_t nl = params.low_pass_length.value_or(newnp);
|
|
633
|
+
if (nl % 2 == 0 && !params.low_pass_length.has_value()) {
|
|
553
634
|
nl += 1;
|
|
554
635
|
}
|
|
555
636
|
|
|
556
|
-
|
|
557
|
-
|
|
637
|
+
size_t ni = params.inner_loops.value_or(params.robust ? 1 : 2);
|
|
638
|
+
size_t no = params.outer_loops.value_or(params.robust ? 15 : 0);
|
|
558
639
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
640
|
+
size_t nsjump = params.seasonal_jump.value_or(
|
|
641
|
+
static_cast<size_t>(std::ceil(static_cast<float>(newns) / 10.0))
|
|
642
|
+
);
|
|
643
|
+
size_t ntjump = params.trend_jump.value_or(
|
|
644
|
+
static_cast<size_t>(std::ceil(static_cast<float>(nt) / 10.0))
|
|
645
|
+
);
|
|
646
|
+
size_t nljump = params.low_pass_jump.value_or(
|
|
647
|
+
static_cast<size_t>(std::ceil(static_cast<float>(nl) / 10.0))
|
|
648
|
+
);
|
|
562
649
|
|
|
563
|
-
stl(
|
|
650
|
+
detail::stl(
|
|
651
|
+
y,
|
|
652
|
+
newnp,
|
|
653
|
+
newns,
|
|
654
|
+
nt,
|
|
655
|
+
nl,
|
|
656
|
+
isdeg,
|
|
657
|
+
itdeg,
|
|
658
|
+
ildeg,
|
|
659
|
+
nsjump,
|
|
660
|
+
ntjump,
|
|
661
|
+
nljump,
|
|
662
|
+
ni,
|
|
663
|
+
no,
|
|
664
|
+
weights,
|
|
665
|
+
seasonal,
|
|
666
|
+
trend
|
|
667
|
+
);
|
|
564
668
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
669
|
+
remainder.reserve(n);
|
|
670
|
+
// TODO use std::views::zip for C++23
|
|
671
|
+
for (size_t i = 0; i < y.size(); i++) {
|
|
672
|
+
remainder.push_back(detail::span_at(y, i) - seasonal.at(i) - trend.at(i));
|
|
568
673
|
}
|
|
569
674
|
|
|
570
|
-
|
|
675
|
+
seasonal_ = std::move(seasonal);
|
|
676
|
+
trend_ = std::move(trend);
|
|
677
|
+
remainder_ = std::move(remainder);
|
|
678
|
+
weights_ = std::move(weights);
|
|
571
679
|
}
|
|
572
680
|
|
|
573
681
|
template<typename T>
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
}
|
|
682
|
+
Stl<T>::Stl(const std::vector<T>& series, size_t period, const StlParams& params) :
|
|
683
|
+
Stl(std::span{series}, period, params) {}
|
|
577
684
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
685
|
+
/// A set of MSTL parameters.
|
|
686
|
+
struct MstlParams {
|
|
687
|
+
/// Sets the number of iterations.
|
|
688
|
+
size_t iterations = 2;
|
|
689
|
+
/// Sets lambda for Box-Cox transformation.
|
|
690
|
+
std::optional<float> lambda = std::nullopt;
|
|
691
|
+
/// Sets the lengths of the seasonal smoothers.
|
|
692
|
+
std::optional<std::vector<size_t>> seasonal_lengths = std::nullopt;
|
|
693
|
+
/// Sets the STL parameters.
|
|
694
|
+
StlParams stl_params = StlParams();
|
|
695
|
+
};
|
|
584
696
|
|
|
585
|
-
///
|
|
697
|
+
/// Multiple seasonal-trend decomposition using Loess (MSTL).
|
|
586
698
|
template<typename T = float>
|
|
587
|
-
class
|
|
588
|
-
public:
|
|
699
|
+
class Mstl {
|
|
700
|
+
public:
|
|
701
|
+
/// Decomposes a time series from a vector.
|
|
702
|
+
Mstl(
|
|
703
|
+
const std::vector<T>& series,
|
|
704
|
+
const std::vector<size_t>& periods,
|
|
705
|
+
const MstlParams& params = MstlParams()
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
/// Decomposes a time series from a span.
|
|
709
|
+
Mstl(
|
|
710
|
+
std::span<const T> series,
|
|
711
|
+
std::span<const size_t> periods,
|
|
712
|
+
const MstlParams& params = MstlParams()
|
|
713
|
+
);
|
|
714
|
+
|
|
589
715
|
/// Returns the seasonal component.
|
|
590
|
-
std::vector<std::vector<T
|
|
716
|
+
const std::vector<std::vector<T>>& seasonal() const {
|
|
717
|
+
return seasonal_;
|
|
718
|
+
}
|
|
591
719
|
|
|
592
720
|
/// Returns the trend component.
|
|
593
|
-
std::vector<T
|
|
721
|
+
const std::vector<T>& trend() const {
|
|
722
|
+
return trend_;
|
|
723
|
+
}
|
|
594
724
|
|
|
595
725
|
/// Returns the remainder.
|
|
596
|
-
std::vector<T
|
|
726
|
+
const std::vector<T>& remainder() const {
|
|
727
|
+
return remainder_;
|
|
728
|
+
}
|
|
597
729
|
|
|
598
730
|
/// Returns the seasonal strength.
|
|
599
|
-
|
|
731
|
+
std::vector<double> seasonal_strength() const {
|
|
600
732
|
std::vector<double> res;
|
|
601
|
-
|
|
602
|
-
|
|
733
|
+
res.reserve(seasonal_.size());
|
|
734
|
+
for (const auto& s : seasonal_) {
|
|
735
|
+
res.push_back(detail::strength(s, remainder_));
|
|
603
736
|
}
|
|
604
737
|
return res;
|
|
605
738
|
}
|
|
606
739
|
|
|
607
740
|
/// Returns the trend strength.
|
|
608
|
-
|
|
609
|
-
return strength(
|
|
610
|
-
}
|
|
611
|
-
};
|
|
612
|
-
|
|
613
|
-
/// A set of MSTL parameters.
|
|
614
|
-
class MstlParams {
|
|
615
|
-
size_t iterate_ = 2;
|
|
616
|
-
std::optional<float> lambda_ = std::nullopt;
|
|
617
|
-
std::optional<std::vector<size_t>> swin_ = std::nullopt;
|
|
618
|
-
StlParams stl_params_;
|
|
619
|
-
|
|
620
|
-
public:
|
|
621
|
-
/// Sets the number of iterations.
|
|
622
|
-
inline MstlParams iterations(size_t iterations) {
|
|
623
|
-
this->iterate_ = iterations;
|
|
624
|
-
return *this;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
/// Sets lambda for Box-Cox transformation.
|
|
628
|
-
inline MstlParams lambda(float lambda) {
|
|
629
|
-
this->lambda_ = lambda;
|
|
630
|
-
return *this;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/// Sets the lengths of the seasonal smoothers.
|
|
634
|
-
inline MstlParams seasonal_lengths(const std::vector<size_t>& lengths) {
|
|
635
|
-
this->swin_ = lengths;
|
|
636
|
-
return *this;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
/// Sets the STL parameters.
|
|
640
|
-
inline MstlParams stl_params(const StlParams& stl_params) {
|
|
641
|
-
this->stl_params_ = stl_params;
|
|
642
|
-
return *this;
|
|
741
|
+
double trend_strength() const {
|
|
742
|
+
return detail::strength(trend_, remainder_);
|
|
643
743
|
}
|
|
644
744
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
/// Decomposes a time series from a vector.
|
|
650
|
-
template<typename T>
|
|
651
|
-
MstlResult<T> fit(const std::vector<T>& series, const std::vector<size_t>& periods) const;
|
|
652
|
-
|
|
653
|
-
#if __cplusplus >= 202002L
|
|
654
|
-
/// Decomposes a time series from a span.
|
|
655
|
-
template<typename T>
|
|
656
|
-
MstlResult<T> fit(std::span<const T> series, std::span<const size_t> periods) const;
|
|
657
|
-
#endif
|
|
745
|
+
private:
|
|
746
|
+
std::vector<std::vector<T>> seasonal_;
|
|
747
|
+
std::vector<T> trend_;
|
|
748
|
+
std::vector<T> remainder_;
|
|
658
749
|
};
|
|
659
750
|
|
|
660
|
-
|
|
661
|
-
inline MstlParams mstl_params() {
|
|
662
|
-
return MstlParams();
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
namespace {
|
|
751
|
+
namespace detail {
|
|
666
752
|
|
|
667
753
|
template<typename T>
|
|
668
|
-
std::vector<T> box_cox(const T
|
|
754
|
+
std::vector<T> box_cox(std::span<const T> y, float lambda) {
|
|
669
755
|
std::vector<T> res;
|
|
670
|
-
res.reserve(
|
|
756
|
+
res.reserve(y.size());
|
|
671
757
|
if (lambda != 0.0) {
|
|
672
|
-
for (
|
|
673
|
-
res.push_back(
|
|
758
|
+
for (auto yi : y) {
|
|
759
|
+
res.push_back(static_cast<T>(std::pow(yi, lambda) - 1.0) / lambda);
|
|
674
760
|
}
|
|
675
761
|
} else {
|
|
676
|
-
for (
|
|
677
|
-
res.push_back(std::log(
|
|
762
|
+
for (auto yi : y) {
|
|
763
|
+
res.push_back(std::log(yi));
|
|
678
764
|
}
|
|
679
765
|
}
|
|
680
766
|
return res;
|
|
@@ -682,10 +768,8 @@ std::vector<T> box_cox(const T* y, size_t y_size, float lambda) {
|
|
|
682
768
|
|
|
683
769
|
template<typename T>
|
|
684
770
|
std::tuple<std::vector<T>, std::vector<T>, std::vector<std::vector<T>>> mstl(
|
|
685
|
-
const T
|
|
686
|
-
size_t
|
|
687
|
-
const size_t* seas_ids,
|
|
688
|
-
size_t seas_size,
|
|
771
|
+
std::span<const T> x,
|
|
772
|
+
std::span<const size_t> seas_ids,
|
|
689
773
|
size_t iterate,
|
|
690
774
|
std::optional<float> lambda,
|
|
691
775
|
const std::optional<std::vector<size_t>>& swin,
|
|
@@ -693,134 +777,126 @@ std::tuple<std::vector<T>, std::vector<T>, std::vector<std::vector<T>>> mstl(
|
|
|
693
777
|
) {
|
|
694
778
|
// keep track of indices instead of sorting seas_ids
|
|
695
779
|
// so order is preserved with seasonality
|
|
696
|
-
std::vector<size_t> indices;
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
std::sort(indices.begin(), indices.end(), [&seas_ids](size_t a, size_t b) {
|
|
701
|
-
return seas_ids[a] < seas_ids[b];
|
|
780
|
+
std::vector<size_t> indices(seas_ids.size());
|
|
781
|
+
std::iota(indices.begin(), indices.end(), 0);
|
|
782
|
+
std::ranges::sort(indices, [&seas_ids](size_t a, size_t b) {
|
|
783
|
+
return span_at(seas_ids, a) < span_at(seas_ids, b);
|
|
702
784
|
});
|
|
703
785
|
|
|
704
|
-
if (
|
|
786
|
+
if (seas_ids.size() == 1) {
|
|
705
787
|
iterate = 1;
|
|
706
788
|
}
|
|
707
789
|
|
|
708
790
|
std::vector<std::vector<T>> seasonality;
|
|
709
|
-
seasonality.reserve(
|
|
791
|
+
seasonality.reserve(seas_ids.size());
|
|
710
792
|
std::vector<T> trend;
|
|
711
793
|
|
|
712
|
-
|
|
794
|
+
std::vector<T> deseas = lambda.has_value()
|
|
795
|
+
? box_cox(x, lambda.value())
|
|
796
|
+
: std::vector<T>(x.begin(), x.end());
|
|
713
797
|
|
|
714
|
-
if (
|
|
715
|
-
for (size_t i = 0; i <
|
|
798
|
+
if (!seas_ids.empty()) {
|
|
799
|
+
for (size_t i = 0; i < seas_ids.size(); i++) {
|
|
716
800
|
seasonality.push_back(std::vector<T>());
|
|
717
801
|
}
|
|
718
802
|
|
|
719
803
|
for (size_t j = 0; j < iterate; j++) {
|
|
720
804
|
for (size_t i = 0; i < indices.size(); i++) {
|
|
721
|
-
|
|
805
|
+
size_t idx = indices.at(i);
|
|
722
806
|
|
|
723
807
|
if (j > 0) {
|
|
724
808
|
for (size_t ii = 0; ii < deseas.size(); ii++) {
|
|
725
|
-
deseas
|
|
809
|
+
deseas.at(ii) += seasonality.at(idx).at(ii);
|
|
726
810
|
}
|
|
727
811
|
}
|
|
728
812
|
|
|
729
|
-
|
|
813
|
+
StlParams params = stl_params;
|
|
730
814
|
if (swin) {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
fit = stl_params.fit(deseas, seas_ids[idx]);
|
|
735
|
-
} else {
|
|
736
|
-
StlParams clone = stl_params;
|
|
737
|
-
fit = clone.seasonal_length(7 + 4 * (i + 1)).fit(deseas, seas_ids[idx]);
|
|
815
|
+
params.seasonal_length = swin.value().at(idx);
|
|
816
|
+
} else if (!stl_params.seasonal_length.has_value()) {
|
|
817
|
+
params.seasonal_length = 7 + 4 * (i + 1);
|
|
738
818
|
}
|
|
819
|
+
Stl<T> fit{deseas, span_at(seas_ids, idx), params};
|
|
739
820
|
|
|
740
|
-
seasonality
|
|
741
|
-
trend = fit.trend;
|
|
821
|
+
seasonality.at(idx) = fit.seasonal();
|
|
822
|
+
trend = fit.trend();
|
|
742
823
|
|
|
743
824
|
for (size_t ii = 0; ii < deseas.size(); ii++) {
|
|
744
|
-
deseas
|
|
825
|
+
deseas.at(ii) -= seasonality.at(idx).at(ii);
|
|
745
826
|
}
|
|
746
827
|
}
|
|
747
828
|
}
|
|
748
829
|
} else {
|
|
749
830
|
// TODO use Friedman's Super Smoother for trend
|
|
750
|
-
throw std::invalid_argument
|
|
831
|
+
throw std::invalid_argument{"periods must not be empty"};
|
|
751
832
|
}
|
|
752
833
|
|
|
753
834
|
std::vector<T> remainder;
|
|
754
|
-
remainder.reserve(
|
|
755
|
-
for (size_t i = 0; i <
|
|
756
|
-
remainder.push_back(deseas
|
|
835
|
+
remainder.reserve(x.size());
|
|
836
|
+
for (size_t i = 0; i < x.size(); i++) {
|
|
837
|
+
remainder.push_back(deseas.at(i) - trend.at(i));
|
|
757
838
|
}
|
|
758
839
|
|
|
759
840
|
return std::make_tuple(trend, remainder, seasonality);
|
|
760
841
|
}
|
|
761
842
|
|
|
762
|
-
}
|
|
843
|
+
} // namespace detail
|
|
763
844
|
|
|
764
845
|
template<typename T>
|
|
765
|
-
|
|
846
|
+
Mstl<T>::Mstl(
|
|
847
|
+
std::span<const T> series,
|
|
848
|
+
std::span<const size_t> periods,
|
|
849
|
+
const MstlParams& params
|
|
850
|
+
) {
|
|
766
851
|
// return error to be consistent with stl
|
|
767
852
|
// and ensure seasonal is always same length as periods
|
|
768
|
-
for (
|
|
769
|
-
if (
|
|
770
|
-
throw std::invalid_argument
|
|
853
|
+
for (auto v : periods) {
|
|
854
|
+
if (v < 2) {
|
|
855
|
+
throw std::invalid_argument{"periods must be at least 2"};
|
|
771
856
|
}
|
|
772
857
|
}
|
|
773
858
|
|
|
774
859
|
// return error to be consistent with stl
|
|
775
860
|
// and ensure seasonal is always same length as periods
|
|
776
|
-
for (
|
|
777
|
-
if (
|
|
778
|
-
throw std::invalid_argument
|
|
861
|
+
for (auto v : periods) {
|
|
862
|
+
if (series.size() < v * 2) {
|
|
863
|
+
throw std::invalid_argument{"series has less than two periods"};
|
|
779
864
|
}
|
|
780
865
|
}
|
|
781
866
|
|
|
782
|
-
if (
|
|
783
|
-
|
|
867
|
+
if (params.lambda.has_value()) {
|
|
868
|
+
float lambda = params.lambda.value();
|
|
784
869
|
if (lambda < 0 || lambda > 1) {
|
|
785
|
-
throw std::invalid_argument
|
|
870
|
+
throw std::invalid_argument{"lambda must be between 0 and 1"};
|
|
786
871
|
}
|
|
787
872
|
}
|
|
788
873
|
|
|
789
|
-
if (
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
throw std::invalid_argument("seasonal_lengths must have the same length as periods");
|
|
874
|
+
if (params.seasonal_lengths.has_value()) {
|
|
875
|
+
if (params.seasonal_lengths.value().size() != periods.size()) {
|
|
876
|
+
throw std::invalid_argument{"seasonal_lengths must have the same length as periods"};
|
|
793
877
|
}
|
|
794
878
|
}
|
|
795
879
|
|
|
796
|
-
auto [trend, remainder, seasonal] = mstl(
|
|
880
|
+
auto [trend, remainder, seasonal] = detail::mstl(
|
|
797
881
|
series,
|
|
798
|
-
series_size,
|
|
799
882
|
periods,
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
stl_params_
|
|
883
|
+
params.iterations,
|
|
884
|
+
params.lambda,
|
|
885
|
+
params.seasonal_lengths,
|
|
886
|
+
params.stl_params
|
|
805
887
|
);
|
|
806
888
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
remainder
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
template<typename T>
|
|
815
|
-
MstlResult<T> MstlParams::fit(const std::vector<T>& series, const std::vector<size_t>& periods) const {
|
|
816
|
-
return MstlParams::fit(series.data(), series.size(), periods.data(), periods.size());
|
|
889
|
+
seasonal_ = std::move(seasonal);
|
|
890
|
+
trend_ = std::move(trend);
|
|
891
|
+
remainder_ = std::move(remainder);
|
|
817
892
|
}
|
|
818
893
|
|
|
819
|
-
#if __cplusplus >= 202002L
|
|
820
894
|
template<typename T>
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
}
|
|
895
|
+
Mstl<T>::Mstl(
|
|
896
|
+
const std::vector<T>& series,
|
|
897
|
+
const std::vector<size_t>& periods,
|
|
898
|
+
const MstlParams& params
|
|
899
|
+
) :
|
|
900
|
+
Mstl(std::span{series}, std::span{periods}, params) {}
|
|
901
|
+
|
|
902
|
+
} // namespace stl
|