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.
data/ext/stl/stl.hpp CHANGED
@@ -1,5 +1,5 @@
1
- /*!
2
- * STL C++ v0.2.0
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
- bool est(const T* y, size_t n, size_t len, int ideg, T xs, T* ys, size_t nleft, size_t nright, T* w, bool userw, const T* rw) {
37
- auto range = ((T) n) - 1.0;
38
- auto h = std::max(xs - ((T) nleft), ((T) nright) - xs);
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 += (T) ((len - n) / 2);
61
+ h += static_cast<T>((len - n) / 2);
42
62
  }
43
63
 
44
- auto h9 = 0.999 * h;
45
- auto h1 = 0.001 * h;
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
- auto a = 0.0;
49
- for (auto j = nleft; j <= nright; j++) {
50
- w[j - 1] = 0.0;
51
- auto r = std::abs(((T) j) - xs);
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[j - 1] = 1.0;
74
+ w.at(j - 1) = 1.0;
55
75
  } else {
56
- w[j - 1] = (T) std::pow(1.0 - std::pow(r / h, 3), 3);
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[j - 1] *= rw[j - 1];
79
+ w.at(j - 1) *= rw.at(j - 1);
60
80
  }
61
- a += w[j - 1];
81
+ a += w.at(j - 1);
62
82
  }
63
83
  }
64
84
 
65
85
  if (a <= 0.0) {
66
86
  return false;
67
- } else { // weighted least squares
68
- for (auto j = nleft; j <= nright; j++) { // make sum of w(j) == 1
69
- w[j - 1] /= (T) a;
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) { // use linear fit
73
- auto a = 0.0;
74
- for (auto j = nleft; j <= nright; j++) { // weighted center of x values
75
- a += w[j - 1] * ((T) j);
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
- auto b = xs - a;
78
- auto c = 0.0;
79
- for (auto j = nleft; j <= nright; j++) {
80
- c += w[j - 1] * std::pow(((T) j) - a, 2);
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 (auto j = nleft; j <= nright; j++) {
87
- w[j - 1] *= (T) (b * (((T) j) - a) + 1.0);
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
- *ys = 0.0;
93
- for (auto j = nleft; j <= nright; j++) {
94
- *ys += w[j - 1] * y[j - 1];
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(const T* y, size_t n, size_t len, int ideg, size_t njump, bool userw, const T* rw, T* ys, T* res) {
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[0] = y[0];
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
- auto newnj = std::min(njump, n - 1);
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
- auto ok = est(y, n, len, ideg, (T) i, &ys[i - 1], nleft, nright, res, userw, rw);
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[i - 1] = y[i - 1];
155
+ span_at(ys, i - 1) = y.at(i - 1);
119
156
  }
120
157
  }
121
- } else if (newnj == 1) { // newnj equal to one, len less than n
122
- auto nsh = (len + 1) / 2;
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++) { // fitted value at 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
- auto ok = est(y, n, len, ideg, (T) i, &ys[i - 1], nleft, nright, res, userw, rw);
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[i - 1] = y[i - 1];
174
+ span_at(ys, i - 1) = y.at(i - 1);
133
175
  }
134
176
  }
135
- } else { // newnj greater than one, len less than n
136
- auto nsh = (len + 1) / 2;
137
- for (size_t i = 1; i <= n; i += newnj) { // fitted value at i
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
- auto ok = est(y, n, len, ideg, (T) i, &ys[i - 1], nleft, nright, res, userw, rw);
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[i - 1] = y[i - 1];
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
- auto delta = (ys[i + newnj - 1] - ys[i - 1]) / ((T) newnj);
158
- for (auto j = i + 1; j <= i + newnj - 1; j++) {
159
- ys[j - 1] = ys[i - 1] + delta * ((T) (j - i));
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
- auto k = ((n - 1) / newnj) * newnj + 1;
209
+ size_t k = ((n - 1) / newnj) * newnj + 1;
163
210
  if (k != n) {
164
- auto ok = est(y, n, len, ideg, (T) n, &ys[n - 1], nleft, nright, res, userw, rw);
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[n - 1] = y[n - 1];
216
+ span_at(ys, n - 1) = y.at(n - 1);
167
217
  }
168
218
  if (k != n - 1) {
169
- auto delta = (ys[n - 1] - ys[k - 1]) / ((T) (n - k));
170
- for (auto j = k + 1; j <= n - 1; j++) {
171
- ys[j - 1] = ys[k - 1] + delta * ((T) (j - k));
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* x, size_t n, size_t len, T* ave) {
180
- auto newn = n - len + 1;
181
- double flen = (T) len;
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[i];
236
+ v += x.at(i);
187
237
  }
188
238
 
189
- ave[0] = (T) (v / flen);
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[m] + x[k];
196
- ave[j] = (T) (v / flen);
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(const T* x, size_t n, size_t np, T* trend, T* work) {
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* y, size_t n, const T* fit, T* rw) {
212
- for (size_t i = 0; i < n; i++) {
213
- rw[i] = std::abs(y[i] - fit[i]);
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
- auto mid1 = (n - 1) / 2;
217
- auto mid2 = n / 2;
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, rw + n);
278
+ std::ranges::sort(rw);
221
279
 
222
- auto cmad = 3.0 * (rw[mid1] + rw[mid2]); // 6 * median abs resid
223
- auto c9 = 0.999 * cmad;
224
- auto c1 = 0.001 * cmad;
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
- for (size_t i = 0; i < n; i++) {
227
- auto r = std::abs(y[i] - fit[i]);
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[i] = 1.0;
288
+ rw.at(i) = 1.0;
230
289
  } else if (r <= c9) {
231
- rw[i] = (T) std::pow(1.0 - std::pow(r / cmad, 2), 2);
290
+ rw.at(i) = static_cast<T>(std::pow(1.0 - std::pow(r / cmad, 2.0), 2.0));
232
291
  } else {
233
- rw[i] = 0.0;
292
+ rw.at(i) = 0.0;
234
293
  }
235
294
  }
236
295
  }
237
296
 
238
297
  template<typename T>
239
- void ss(const T* y, size_t n, size_t np, size_t ns, int isdeg, size_t nsjump, bool userw, T* rw, T* season, T* work1, T* work2, T* work3, T* work4) {
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[i - 1] = y[(i - 1) * np + j - 1];
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[i - 1] = rw[(i - 1) * np + j - 1];
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 + 1, work4);
324
+ ess(work1, k, ns, isdeg, nsjump, userw, work3, std::span{work2}.subspan(1), work4);
252
325
  T xs = 0.0;
253
- auto nright = std::min(ns, k);
254
- auto ok = est(work1, k, ns, isdeg, xs, &work2[0], 1, nright, work4, userw, work3);
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[0] = work2[1];
329
+ work2.at(0) = work2.at(1);
257
330
  }
258
- xs = k + 1;
259
- size_t nleft = (size_t) std::max(1, (int) k - (int) ns + 1);
260
- ok = est(work1, k, ns, isdeg, xs, &work2[k + 1], nleft, k, work4, userw, work3);
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[k + 1] = work2[k];
337
+ work2.at(k + 1) = work2.at(k);
263
338
  }
264
339
  for (size_t m = 1; m <= k + 2; m++) {
265
- season[(m - 1) * np + j - 1] = work2[m - 1];
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(const T* y, size_t n, size_t np, size_t ns, size_t nt, size_t nl, int isdeg, int itdeg, int ildeg, size_t nsjump, size_t ntjump, size_t nljump, size_t ni, bool userw, T* rw, T* season, T* trend, T* work1, T* work2, T* work3, T* work4, T* work5) {
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
- for (size_t i = 0; i < n; i++) {
274
- work1[i] = y[i] - trend[i];
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[i] = work2[np + i] - work1[i];
382
+ season.at(i) = work2.at(np + i) - work1.at(i);
282
383
  }
283
- for (size_t i = 0; i < n; i++) {
284
- work1[i] = y[i] - season[i];
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(const T* y, size_t n, size_t np, size_t ns, size_t nt, size_t nl, int isdeg, int itdeg, int ildeg, size_t nsjump, size_t ntjump, size_t nljump, size_t ni, size_t no, T* rw, T* season, T* trend) {
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("seasonal_length must be at least 3");
414
+ throw std::invalid_argument{"seasonal_length must be at least 3"};
294
415
  }
295
416
  if (nt < 3) {
296
- throw std::invalid_argument("trend_length must be at least 3");
417
+ throw std::invalid_argument{"trend_length must be at least 3"};
297
418
  }
298
419
  if (nl < 3) {
299
- throw std::invalid_argument("low_pass_length must be at least 3");
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("period must be at least 2");
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("seasonal_degree must be 0 or 1");
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("trend_degree must be 0 or 1");
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("low_pass_degree must be 0 or 1");
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("seasonal_length must be odd");
437
+ throw std::invalid_argument{"seasonal_length must be odd"};
317
438
  }
318
439
  if (nt % 2 != 1) {
319
- throw std::invalid_argument("trend_length must be odd");
440
+ throw std::invalid_argument{"trend_length must be odd"};
320
441
  }
321
442
  if (nl % 2 != 1) {
322
- throw std::invalid_argument("low_pass_length must be odd");
443
+ throw std::invalid_argument{"low_pass_length must be odd"};
323
444
  }
324
445
 
325
- auto work1 = std::vector<T>(n + 2 * np);
326
- auto work2 = std::vector<T>(n + 2 * np);
327
- auto work3 = std::vector<T>(n + 2 * np);
328
- auto work4 = std::vector<T>(n + 2 * np);
329
- auto work5 = std::vector<T>(n + 2 * np);
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
- auto userw = false;
452
+ bool userw = false;
332
453
  size_t k = 0;
333
454
 
334
455
  while (true) {
335
- onestp(y, n, np, ns, nt, nl, isdeg, itdeg, ildeg, nsjump, ntjump, nljump, ni, userw, rw, season, trend, work1.data(), work2.data(), work3.data(), work4.data(), work5.data());
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[i] = trend[i] + season[i];
484
+ work1.at(i) = trend.at(i) + season.at(i);
342
485
  }
343
- rwts(y, n, work1.data(), rw);
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[i] = 1.0;
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
- auto mean = std::accumulate(series.begin(), series.end(), 0.0) / series.size();
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[i] + remainder[i]);
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
- class StlParams {
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
- inline StlParams seasonal_length(size_t length) {
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
- inline StlParams trend_length(size_t length) {
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
- inline StlParams low_pass_length(size_t length) {
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
- inline StlParams seasonal_degree(int degree) {
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
- inline StlParams trend_degree(int degree) {
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
- inline StlParams low_pass_degree(int degree) {
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
- inline StlParams seasonal_jump(size_t jump) {
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
- inline StlParams trend_jump(size_t jump) {
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
- inline StlParams low_pass_jump(size_t jump) {
473
- this->nljump_ = jump;
474
- return *this;
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
- /// Sets the number of loops for updating the seasonal and trend components.
478
- inline StlParams inner_loops(size_t loops) {
479
- this->ni_ = loops;
480
- return *this;
564
+ /// Returns the trend component.
565
+ const std::vector<T>& trend() const {
566
+ return trend_;
481
567
  }
482
568
 
483
- /// Sets the number of iterations of robust fitting.
484
- inline StlParams outer_loops(size_t loops) {
485
- this->no_ = loops;
486
- return *this;
569
+ /// Returns the remainder.
570
+ const std::vector<T>& remainder() const {
571
+ return remainder_;
487
572
  }
488
573
 
489
- /// Sets whether robustness iterations are to be used.
490
- inline StlParams robust(bool robust) {
491
- this->robust_ = robust;
492
- return *this;
574
+ /// Returns the weights.
575
+ const std::vector<T>& weights() const {
576
+ return weights_;
493
577
  }
494
578
 
495
- /// Decomposes a time series from an array.
496
- template<typename T>
497
- StlResult<T> fit(const T* series, size_t series_size, size_t period) const;
579
+ /// Returns the seasonal strength.
580
+ double seasonal_strength() const {
581
+ return detail::strength(seasonal_, remainder_);
582
+ }
498
583
 
499
- /// Decomposes a time series from a vector.
500
- template<typename T>
501
- StlResult<T> fit(const std::vector<T>& series, size_t period) const;
584
+ /// Returns the trend strength.
585
+ double trend_strength() const {
586
+ return detail::strength(trend_, remainder_);
587
+ }
502
588
 
503
- #if __cplusplus >= 202002L
504
- /// Decomposes a time series from a span.
505
- template<typename T>
506
- StlResult<T> fit(std::span<const T> series, size_t period) const;
507
- #endif
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
- StlResult<T> StlParams::fit(const T* series, size_t series_size, size_t period) const {
517
- auto y = series;
518
- auto np = period;
519
- auto n = series_size;
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 < 2 * np) {
522
- throw std::invalid_argument("series has less than two periods");
602
+ if (n / 2 < np) {
603
+ throw std::invalid_argument{"series has less than two periods"};
523
604
  }
524
605
 
525
- auto ns = this->ns_.value_or(np);
606
+ size_t ns = params.seasonal_length.value_or(np);
526
607
 
527
- auto isdeg = this->isdeg_;
528
- auto itdeg = this->itdeg_;
608
+ int isdeg = params.seasonal_degree;
609
+ int itdeg = params.trend_degree;
529
610
 
530
- auto res = StlResult<T> {
531
- std::vector<T>(n),
532
- std::vector<T>(n),
533
- std::vector<T>(),
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
- auto ildeg = this->ildeg_.value_or(itdeg);
538
- auto newns = std::max(ns, (size_t) 3);
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
- auto newnp = std::max(np, (size_t) 2);
544
- auto nt = (size_t) ceil((1.5 * newnp) / (1.0 - 1.5 / (float) newns));
545
- nt = this->nt_.value_or(nt);
546
- nt = std::max(nt, (size_t) 3);
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
- auto nl = this->nl_.value_or(newnp);
552
- if (nl % 2 == 0 && !this->nl_.has_value()) {
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
- auto ni = this->ni_.value_or(this->robust_ ? 1 : 2);
557
- auto no = this->no_.value_or(this->robust_ ? 15 : 0);
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
- auto nsjump = this->nsjump_.value_or((size_t) ceil(((float) newns) / 10.0));
560
- auto ntjump = this->ntjump_.value_or((size_t) ceil(((float) nt) / 10.0));
561
- auto nljump = this->nljump_.value_or((size_t) ceil(((float) nl) / 10.0));
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(y, n, newnp, newns, nt, nl, isdeg, itdeg, ildeg, nsjump, ntjump, nljump, ni, no, res.weights.data(), res.seasonal.data(), res.trend.data());
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
- res.remainder.reserve(n);
566
- for (size_t i = 0; i < n; i++) {
567
- res.remainder.push_back(y[i] - res.seasonal[i] - res.trend[i]);
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
- return res;
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
- StlResult<T> StlParams::fit(const std::vector<T>& series, size_t period) const {
575
- return StlParams::fit(series.data(), series.size(), period);
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
- #if __cplusplus >= 202002L
579
- template<typename T>
580
- StlResult<T> StlParams::fit(std::span<const T> series, size_t period) const {
581
- return StlParams::fit(series.data(), series.size(), period);
582
- }
583
- #endif
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
- /// A MSTL result.
697
+ /// Multiple seasonal-trend decomposition using Loess (MSTL).
586
698
  template<typename T = float>
587
- class MstlResult {
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>> seasonal;
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> trend;
721
+ const std::vector<T>& trend() const {
722
+ return trend_;
723
+ }
594
724
 
595
725
  /// Returns the remainder.
596
- std::vector<T> remainder;
726
+ const std::vector<T>& remainder() const {
727
+ return remainder_;
728
+ }
597
729
 
598
730
  /// Returns the seasonal strength.
599
- inline std::vector<double> seasonal_strength() const {
731
+ std::vector<double> seasonal_strength() const {
600
732
  std::vector<double> res;
601
- for (auto& s : seasonal) {
602
- res.push_back(strength(s, remainder));
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
- inline double trend_strength() const {
609
- return strength(trend, remainder);
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
- /// Decomposes a time series from an array.
646
- template<typename T>
647
- MstlResult<T> fit(const T* series, size_t series_size, const size_t* periods, size_t periods_size) const;
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
- /// Creates a new set of MSTL parameters.
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* y, size_t y_size, float lambda) {
754
+ std::vector<T> box_cox(std::span<const T> y, float lambda) {
669
755
  std::vector<T> res;
670
- res.reserve(y_size);
756
+ res.reserve(y.size());
671
757
  if (lambda != 0.0) {
672
- for (size_t i = 0; i < y_size; i++) {
673
- res.push_back((T) (std::pow(y[i], lambda) - 1.0) / lambda);
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 (size_t i = 0; i < y_size; i++) {
677
- res.push_back(std::log(y[i]));
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* x,
686
- size_t k,
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
- for (size_t i = 0; i < seas_size; i++) {
698
- indices.push_back(i);
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 (seas_size == 1) {
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(seas_size);
791
+ seasonality.reserve(seas_ids.size());
710
792
  std::vector<T> trend;
711
793
 
712
- auto deseas = lambda.has_value() ? box_cox(x, k, lambda.value()) : std::vector<T>(x, x + k);
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 (seas_size != 0) {
715
- for (size_t i = 0; i < seas_size; 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
- auto idx = indices[i];
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[ii] += seasonality[idx][ii];
809
+ deseas.at(ii) += seasonality.at(idx).at(ii);
726
810
  }
727
811
  }
728
812
 
729
- StlResult<T> fit;
813
+ StlParams params = stl_params;
730
814
  if (swin) {
731
- StlParams clone = stl_params;
732
- fit = clone.seasonal_length((*swin)[idx]).fit(deseas, seas_ids[idx]);
733
- } else if (stl_params.ns_.has_value()) {
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[idx] = fit.seasonal;
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[ii] -= seasonality[idx][ii];
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("periods must not be empty");
831
+ throw std::invalid_argument{"periods must not be empty"};
751
832
  }
752
833
 
753
834
  std::vector<T> remainder;
754
- remainder.reserve(k);
755
- for (size_t i = 0; i < k; i++) {
756
- remainder.push_back(deseas[i] - trend[i]);
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
- MstlResult<T> MstlParams::fit(const T* series, size_t series_size, const size_t* periods, size_t periods_size) const {
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 (size_t i = 0; i < periods_size; i++) {
769
- if (periods[i] < 2) {
770
- throw std::invalid_argument("periods must be at least 2");
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 (size_t i = 0; i < periods_size; i++) {
777
- if (series_size < periods[i] * 2) {
778
- throw std::invalid_argument("series has less than two periods");
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 (lambda_.has_value()) {
783
- auto lambda = lambda_.value();
867
+ if (params.lambda.has_value()) {
868
+ float lambda = params.lambda.value();
784
869
  if (lambda < 0 || lambda > 1) {
785
- throw std::invalid_argument("lambda must be between 0 and 1");
870
+ throw std::invalid_argument{"lambda must be between 0 and 1"};
786
871
  }
787
872
  }
788
873
 
789
- if (swin_.has_value()) {
790
- auto swin = swin_.value();
791
- if (swin.size() != periods_size) {
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
- periods_size,
801
- iterate_,
802
- lambda_,
803
- swin_,
804
- stl_params_
883
+ params.iterations,
884
+ params.lambda,
885
+ params.seasonal_lengths,
886
+ params.stl_params
805
887
  );
806
888
 
807
- return MstlResult<T> {
808
- seasonal,
809
- trend,
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
- MstlResult<T> MstlParams::fit(std::span<const T> series, std::span<const size_t> periods) const {
822
- return MstlParams::fit(series.data(), series.size(), periods.data(), periods.size());
823
- }
824
- #endif
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