API Documentation
Loading...
Searching...
No Matches
Spline.hpp
Go to the documentation of this file.
1#include <NDEVR/Angle.h>
2#include <NDEVR/Buffer.h>
3#include <NDEVR/Vertex.h>
4#include <NDEVR/BandMatrix.h>
5#include <NDEVR/SplineBoundaryType.h>
6namespace NDEVR
7{
8 /**--------------------------------------------------------------------------------------------------
9 \brief A spline is a function used to interpolate or smooth data.
10 Splines are a series of polynomials joined at knots
11 **/
12 template<class t_point_type>
13 class Spline
14 {
15 protected:
17 Buffer<t_point_type> m_y; // x,y coordinates of points
18 // interpolation parameters
19 // f(x) = a_i + b_i*(x-x_i) + c_i*(x-x_i)^2 + d_i*(x-x_i)^3
20 // where a_i = y_i, or else it won't go through grid points
23 Buffer<t_point_type> m_d; // spline coefficients
24 t_point_type m_c0; // for left extrapolation
30 bool m_made_monotonic = false;
31 public:
32 // default constructor: set boundary condition to be zero curvature
33 // at both ends, i.e. natural splines
35 {}
36
40 , fltp08 left_value = 0.0
42 , fltp08 right_value = 0.0)
43 : m_type(type)
44 , m_left(left)
45 , m_right(right)
46 , m_left_value(left_value)
47 , m_right_value(right_value)
48 {
50 X.setSize(Y.size());
51 for (uint04 i = 0; i < Y.size(); i++)
52 X[i] = cast<fltp08>(i);
54 }
56 , const Buffer<t_point_type>& Y
58 , bool make_monotonic = false
60 , fltp08 left_value = 0.0
62 , fltp08 right_value = 0.0)
63 : m_type(type)
64 , m_left(left)
65 , m_right(right)
66 , m_left_value(left_value)
67 , m_right_value(right_value)
68 {
70 if (make_monotonic)
72 }
73
75 {
76 lib_assert(x.size() == y.size(), "Y size must be = X size");
77 lib_assert(x.size() >= 3, "X size must be greater");
79 lib_assert(x.size() >= 4, "not-a-knot with 3 points has multiple solutions");
80 m_type = type;
81 m_made_monotonic = false;
82 m_x = x;
83 m_y = y;
84 uint04 n = x.size();
85 for (uint04 i = 0; i < n - 1; i++)
86 lib_assert(m_x[i] < m_x[i + 1], "x must have monotonicity");
87
88
89 if (type == SplineType::e_linear)
90 {
91 // e_linear interpolation
92 m_d.resize(n);
93 m_c.resize(n);
94 m_b.resize(n);
95 for (uint04 i = 0; i < n - 1; i++)
96 {
97 m_d[i] = 0.0;
98 m_c[i] = 0.0;
99 m_b[i] = (m_y[i + 1] - m_y[i]) / (m_x[i + 1] - m_x[i]);
100 }
101 // ignore boundary conditions, set slope equal to the last segment
102 m_b[n - 1] = m_b[n - 2];
103 m_c[n - 1] = 0.0;
104 m_d[n - 1] = 0.0;
105 }
106 else if (type == SplineType::e_cspline)
107 {
108 // classical cubic splines which are C^2 (twice cont differentiable)
109 // this requires solving an equation system
110
111 // setting up the matrix and right hand side of the equation system
112 // for the parameters b[]
113 int n_upper = (m_left == SplineBoundaryType::e_not_a_knot) ? 2 : 1;
114 int n_lower = (m_right == SplineBoundaryType::e_not_a_knot) ? 2 : 1;
115 BandMatrix<fltp08> A(n, n_upper, n_lower);
117 rhs.setSize(n);
118 for (uint04 i = 1; i < n - 1; i++)
119 {
120 A(i, i - 1) = 1.0 / 3.0 * (x[i] - x[i - 1]);
121 A(i, i) = 2.0 / 3.0 * (x[i + 1] - x[i - 1]);
122 A(i, i + 1) = 1.0 / 3.0 * (x[i + 1] - x[i]);
123 rhs[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]) - (y[i] - y[i - 1]) / (x[i] - x[i - 1]);
124 }
125 // boundary conditions
127 {
128 // 2*c[0] = f''
129 A(0, 0) = 2.0;
130 A(0, 1) = 0.0;
131 rhs[0] = m_left_value;
132 }
134 {
135 // b[0] = f', needs to be re-expressed in terms of c:
136 // (2c[0]+c[1])(x[1]-x[0]) = 3 ((y[1]-y[0])/(x[1]-x[0]) - f')
137 A(0, 0) = 2.0 * (x[1] - x[0]);
138 A(0, 1) = 1.0 * (x[1] - x[0]);
139 rhs[0] = 3.0 * ((y[1] - y[0]) / (x[1] - x[0]) - m_left_value);
140 }
142 {
143 // f'''(x[1]) exists, i.e. d[0]=d[1], or re-expressed in c:
144 // -h1*c[0] + (h0+h1)*c[1] - h0*c[2] = 0
145 A(0, 0) = -(x[2] - x[1]);
146 A(0, 1) = x[2] - x[0];
147 A(0, 2) = -(x[1] - x[0]);
148 rhs[0] = 0.0;
149 }
150 else
151 {
152 lib_assert(false, "unknown state");
153 }
155 {
156 // 2*c[n-1] = f''
157 A(n - 1, n - 1) = 2.0;
158 A(n - 1, n - 2) = 0.0;
159 rhs[n - 1] = m_right_value;
160 }
162 {
163 // b[n-1] = f', needs to be re-expressed in terms of c:
164 // (c[n-2]+2c[n-1])(x[n-1]-x[n-2])
165 // = 3 (f' - (y[n-1]-y[n-2])/(x[n-1]-x[n-2]))
166 A(n - 1, n - 1) = 2.0 * (x[n - 1] - x[n - 2]);
167 A(n - 1, n - 2) = 1.0 * (x[n - 1] - x[n - 2]);
168 rhs[n - 1] = 3.0 * (m_right_value - (y[n - 1] - y[n - 2]) / (x[n - 1] - x[n - 2]));
169 }
171 {
172 // f'''(x[n-2]) exists, i.e. d[n-3]=d[n-2], or re-expressed in c:
173 // -h_{n-2}*c[n-3] + (h_{n-3}+h_{n-2})*c[n-2] - h_{n-3}*c[n-1] = 0
174 A(n - 1, n - 3) = -(x[n - 1] - x[n - 2]);
175 A(n - 1, n - 2) = x[n - 1] - x[n - 3];
176 A(n - 1, n - 1) = -(x[n - 2] - x[n - 3]);
177 rhs[0] = 0.0;
178 }
179 else
180 {
181 lib_assert(false, "unknown state");
182 }
183
184 // solve the equation system to obtain the parameters c[]
185 m_c = A.luSolve(rhs);
186
187 // calculate parameters b[] and d[] based on c[]
188 m_d.resize(n);
189 m_b.resize(n);
190 for (uint04 i = 0; i < n - 1; i++)
191 {
192 m_d[i] = 1.0 / 3.0 * (m_c[i + 1] - m_c[i]) / (x[i + 1] - x[i]);
193 m_b[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i])
194 - 1.0 / 3.0 * (2.0 * m_c[i] + m_c[i + 1]) * (x[i + 1] - x[i]);
195 }
196 // for the right extrapolation coefficients (zero cubic term)
197 // f_{n-1}(x) = y_{n-1} + b*(x-x_{n-1}) + c*(x-x_{n-1})^2
198 fltp08 h = x[n - 1] - x[n - 2];
199 // m_c[n-1] is determined by the boundary condition
200 m_d[n - 1] = 0.0;
201 m_b[n - 1] = 3.0 * m_d[n - 2] * h * h + 2.0 * m_c[n - 2] * h + m_b[n - 2]; // = f'_{n-2}(x_{n-1})
203 m_c[n - 1] = 0.0; // force e_linear extrapolation
204
205 }
206 else if (type == SplineType::e_cspline_hermite)
207 {
208 // hermite cubic splines which are C^1 (cont. differentiable)
209 // and derivatives are specified on each grid point
210 // (here we use 3-point finite differences)
211 m_b.resize(n);
212 m_c.resize(n);
213 m_d.resize(n);
214 // set b to match 1st order derivative finite difference
215 for (uint04 i = 1; i < n - 1; i++)
216 {
217 const fltp08 h = m_x[i + 1] - m_x[i];
218 const fltp08 hl = m_x[i] - m_x[i - 1];
219 m_b[i] = -h / (hl * (hl + h)) * m_y[i - 1] + (h - hl) / (hl * h) * m_y[i]
220 + hl / (h * (hl + h)) * m_y[i + 1];
221 }
222 // boundary conditions determine b[0] and b[n-1]
224 {
225 m_b[0] = m_left_value;
226 }
228 {
229 const fltp08 h = m_x[1] - m_x[0];
230 m_b[0] = 0.5 * (-m_b[1] - 0.5 * m_left_value * h + 3.0 * (m_y[1] - m_y[0]) / h);
231 }
233 {
234 // f''' continuous at x[1]
235 const fltp08 h0 = m_x[1] - m_x[0];
236 const fltp08 h1 = m_x[2] - m_x[1];
237 m_b[0] = -m_b[1] + 2.0 * (m_y[1] - m_y[0]) / h0
238 + h0 * h0 / (h1 * h1) * (m_b[1] + m_b[2] - 2.0 * (m_y[2] - m_y[1]) / h1);
239 }
240 else
241 {
242 lib_assert(false, "unknown state");
243 }
245 {
246 m_b[n - 1] = m_right_value;
247 m_c[n - 1] = 0.0;
248 }
250 {
251 const fltp08 h = m_x[n - 1] - m_x[n - 2];
252 m_b[n - 1] = 0.5 * (-m_b[n - 2] + 0.5 * m_right_value * h + 3.0 * (m_y[n - 1] - m_y[n - 2]) / h);
253 m_c[n - 1] = 0.5 * m_right_value;
254 }
256 {
257 // f''' continuous at x[n-2]
258 const fltp08 h0 = m_x[n - 2] - m_x[n - 3];
259 const fltp08 h1 = m_x[n - 1] - m_x[n - 2];
260 m_b[n - 1] = -m_b[n - 2] + 2.0 * (m_y[n - 1] - m_y[n - 2]) / h1 + h1 * h1 / (h0 * h0)
261 * (m_b[n - 3] + m_b[n - 2] - 2.0 * (m_y[n - 2] - m_y[n - 3]) / h0);
262 // f'' continuous at x[n-1]: c[n-1] = 3*d[n-2]*h[n-2] + c[n-1]
263 m_c[n - 1] = (m_b[n - 2] + 2.0 * m_b[n - 1]) / h1 - 3.0 * (m_y[n - 1] - m_y[n - 2]) / (h1 * h1);
264 }
265 else
266 {
267 lib_assert(false, "unknown state");
268 }
269 m_d[n - 1] = 0.0;
270
271 // parameters c and d are determined by continuity and differentiability
273
274 }
275 else
276 {
277 lib_assert(false, "unknown state");
278 }
279
280 // for left extrapolation coefficients
281 m_c0 = (m_left == SplineBoundaryType::e_first_deriv) ? t_point_type(0.0) : m_c[0];
282 }
283
285 {
286 lib_assert(m_x.size() == m_y.size(), "unknown state");
287 lib_assert(m_x.size() == m_b.size(), "unknown state");
288 lib_assert(m_x.size() > 2, "unknown state");
289 bool modified = false;
290 const uint04 n = m_x.size();
291 // make sure: input data monotonic increasing --> b_i>=0
292 // input data monotonic decreasing --> b_i<=0
293 for (uint04 i = 0; i < n; i++)
294 {
295 uint04 im1 = getMax(i - 1, 0U);
296 uint04 ip1 = getMin(i + 1, n - 1);
297 if (((m_y[im1] <= m_y[i]) && (m_y[i] <= m_y[ip1]) && m_b[i] < 0.0) ||
298 ((m_y[im1] >= m_y[i]) && (m_y[i] >= m_y[ip1]) && m_b[i] > 0.0)) {
299 modified = true;
300 m_b[i] = 0.0;
301 }
302 }
303 // if input data is monotonic (b[i], b[i+1], avg have all the same sign)
304 // ensure a sufficient criteria for monotonicity is satisfied:
305 // sqrt(b[i]^2+b[i+1]^2) <= 3 |avg|, with avg=(y[i+1]-y[i])/h,
306 for (uint04 i = 0; i < n - 1; i++)
307 {
308 fltp08 h = m_x[i + 1] - m_x[i];
309 for (uint01 n = 0; n < t_point_type::NumberOfDimensions(); n++)
310 {
311 fltp08 avg = (m_y[i + 1][n] - m_y[i][n]) / h;
312 if (avg == 0.0 && (m_b[i][n] != 0.0 || m_b[i + 1][n] != 0.0)) {
313 modified = true;
314 m_b[i][n] = 0.0;
315 m_b[i + 1][n] = 0.0;
316 }
317 else if ((m_b[i][n] >= 0.0 && m_b[i + 1][n] >= 0.0 && avg > 0.0) ||
318 (m_b[i][n] <= 0.0 && m_b[i + 1][n] <= 0.0 && avg < 0.0))
319 {
320
321 // input data is monotonic
322 fltp08 r = sqrt(m_b[i][n] * m_b[i][n] + m_b[i + 1][n] * m_b[i + 1][n]) / abs(avg);
323 if (r > 3.0) {
324 // sufficient criteria for monotonicity: r<=3
325 // adjust b[i] and b[i+1]
326 modified = true;
327 m_b[i][n] *= (3.0 / r);
328 m_b[i + 1][n] *= (3.0 / r);
329 }
330 }
331 }
332 }
333
334 if (modified)
335 {
337 m_made_monotonic = true;
338 }
339 return modified;
340 }
341
342 // return the closest idx so that m_x[idx] <= x (return 0 if x<m_x[0])
344 {
345 auto it = std::upper_bound(m_x.begin(), m_x.end(), x);
346 return getMax(cast<uint04>(it - m_x.begin() - 1), 0U);
347 }
348
349 t_point_type operator() (fltp08 x) const
350 {
351 // polynomial evaluation using Horner's scheme
352 // TODO: consider more numerically accurate algorithms, e.g.:
353 // - Clenshaw
354 // - Even-Odd method by A.C.R. Newbery
355 // - Compensated Horner Scheme
356 uint04 n = m_x.size();
357 uint04 idx = find_closest(x);
358
359 fltp08 h = x - m_x[idx];
360 t_point_type interpol;
361 if (x < m_x[0])
362 {
363 // extrapolation to the left
364 interpol = (m_c0 * h + m_b[0]) * h + m_y[0];
365 }
366 else if (x > m_x[n - 1])
367 {
368 // extrapolation to the right
369 interpol = (m_c[n - 1] * h + m_b[n - 1]) * h + m_y[n - 1];
370 }
371 else
372 {
373 // interpolation
374 interpol = ((m_d[idx] * h + m_c[idx]) * h + m_b[idx]) * h + m_y[idx];
375 }
376 return interpol;
377 }
378
379 t_point_type deriv(uint04 order, fltp08 x) const
380 {
381 lib_assert(order > 0, "unknown state");
382 uint04 n = m_x.size();
383 uint04 idx = find_closest(x);
384
385 fltp08 h = x - m_x[idx];
386 t_point_type interpol;
387 if (x < m_x[0])
388 {
389 // extrapolation to the left
390 switch (order)
391 {
392 case 1:
393 interpol = 2.0 * m_c0 * h + m_b[0];
394 break;
395 case 2:
396 interpol = 2.0 * m_c0;
397 break;
398 default:
399 interpol = 0.0;
400 break;
401 }
402 }
403 else if (x > m_x[n - 1])
404 {
405 // extrapolation to the right
406 switch (order)
407 {
408 case 1:
409 interpol = 2.0 * m_c[n - 1] * h + m_b[n - 1];
410 break;
411 case 2:
412 interpol = 2.0 * m_c[n - 1];
413 break;
414 default:
415 interpol = 0.0;
416 break;
417 }
418 }
419 else
420 {
421 // interpolation
422 switch (order)
423 {
424 case 1:
425 interpol = (3.0 * m_d[idx] * h + 2.0 * m_c[idx]) * h + m_b[idx];
426 break;
427 case 2:
428 interpol = 6.0 * m_d[idx] * h + 2.0 * m_c[idx];
429 break;
430 case 3:
431 interpol = 6.0 * m_d[idx];
432 break;
433 default:
434 interpol = 0.0;
435 break;
436 }
437 }
438 return interpol;
439 }
440
441 Buffer<t_point_type> solve(t_point_type y, bool ignore_extrapolation) const
442 {
443 Buffer<t_point_type> x; // roots for the entire spline
444 Buffer<t_point_type> root; // roots for each piecewise cubic
445 const uint04 n = m_x.size();
446
447 // left extrapolation
448 if (ignore_extrapolation == false)
449 {
450 root = solveCubic(m_y[0] - y, m_b[0], m_c0, t_point_type(0.0), 1);
451 for (uint04 j = 0; j < root.size(); j++)
452 {
453 if (root[j] < 0.0)
454 x.add(m_x[0] + root[j]);
455 }
456 }
457
458 // brute force check if piecewise cubic has roots in their resp. segment
459 // TODO: make more efficient
460 for (uint04 i = 0; i < n - 1; i++)
461 {
462 root = solveCubic(m_y[i] - y, m_b[i], m_c[i], m_d[i], 1);
463 for (uint04 j = 0; j < root.size(); j++)
464 {
465 fltp08 h = (i > 0) ? (m_x[i] - m_x[i - 1]) : 0.0;
466 fltp08 eps = getEPS() * 512.0 * getMin(h, 1.0);
467 if ((-eps <= root[j]) && (root[j] < m_x[i + 1] - m_x[i]))
468 {
469 t_point_type new_root = m_x[i] + root[j];
470 if (x.size() > 0 && x.last() + eps > new_root)
471 {
472 x.last() = new_root; // avoid spurious duplicate roots
473 }
474 else
475 {
476 x.add(new_root);
477 }
478 }
479 }
480 }
481
482 // right extrapolation
483 if (!ignore_extrapolation)
484 {
485 root = solveCubic(m_y[n - 1] - y, m_b[n - 1], m_c[n - 1], t_point_type(0.0), 1);
486 for (uint04 j = 0; j < root.size(); j++)
487 {
488 if (0.0 <= root[j])
489 x.add(m_x[n - 1] + root[j]);
490 }
491 }
492
493 return x;
494 };
495
496
497 // machine precision of a fltp08, i.e. the successor of 1 is 1+eps
498 static fltp08 getEPS()
499 {
500 return 2.2204460492503131e-16;
501 }
502
503 // solutions for a + b*x = 0
504 static Buffer<t_point_type> solveLinear(t_point_type a
505 , t_point_type b)
506 {
507 Buffer<t_point_type> x; // roots
508 if (b == 0.0)
509 {
510 if (a == 0.0)
511 {
512 // 0*x = 0
513 x.resize(1);
514 x[0] = 0.0; // any x solves it but we need to pick one
515 return x;
516 }
517 else
518 {
519 // 0*x + ... = 0, no solution
520 return x;
521 }
522 }
523 else
524 {
525 x.resize(1);
526 x[0] = -a / b;
527 return x;
528 }
529 }
530
531 // solutions for a + b*x + c*x^2 = 0
533 , t_point_type b, t_point_type c
534 , int newton_iter = 0)
535 {
536 if (c == 0.0)
537 return solveLinear(a, b);
538 // rescale so that we solve x^2 + 2p x + q = (x+p)^2 + q - p^2 = 0
539 const t_point_type p = 0.5 * b / c;
540 const t_point_type q = a / c;
541 const t_point_type discr = p * p - q;
542 const fltp08 eps = 0.5 * getEPS();
543 const t_point_type discr_err = (6.0 * (p * p) + 3.0 * abs(q) + abs(discr)) * eps;
544
545 Buffer<t_point_type> x; // roots
546 if (abs(discr) <= discr_err)
547 {
548 // discriminant is zero --> one root
549 x.resize(1);
550 x[0] = -p;
551 }
552 else if (discr < 0.0) {
553 // no root
554 }
555 else {
556 // two roots
557 x.resize(2);
558 for (uint01 n = 0; n < t_point_type::NumberOfDimensions(); n++)
559 {
560 x[0][n] = -p[n] - sqrt(discr[n]);
561 x[1][n] = -p[n] + sqrt(discr[n]);
562 }
563 }
564
565 // improve solution via newton steps
566 for (uint04 i = 0; i < x.size(); i++)
567 {
568 for (int k = 0; k < newton_iter; k++)
569 {
570 t_point_type f = (c * x[i] + b) * x[i] + a;
571 t_point_type f1 = 2.0 * c * x[i] + b;
572 // only adjust if slope is large enough
573 if (abs(f1) > 1e-8)
574 x[i] -= f / f1;
575 }
576 }
577
578 return x;
579 }
580
581 // solutions for the cubic equation: a + b*x +c*x^2 + d*x^3 = 0
582 // this is a naive implementation of the analytic solution without
583 // optimisation for speed or numerical accuracy
584 // newton_iter: number of newton iterations to improve analytical solution
585 // see also
586 // gsl: gsl_poly_solve_cubic() in solveCubic.c
587 // octave: roots.m - via eigenvalues of the Frobenius companion matrix
589 , t_point_type b, t_point_type c
590 , t_point_type d,int newton_iter) const
591 {
592 if (d == 0.0)
593 return solveQuadratic(a, b, c, newton_iter);
594
595 // convert to normalised form: a + bx + cx^2 + x^3 = 0
596 if (d != 1.0) {
597 a /= d;
598 b /= d;
599 c /= d;
600 }
601
602 // convert to depressed cubic: z^3 - 3pz - 2q = 0
603 // via substitution: z = x + c/3
604 const t_point_type p = -(1.0 / 3.0) * b + (1.0 / 9.0) * (c * c);
605 const t_point_type r = 2.0 * (c * c) - 9.0 * b;
606 const t_point_type q = -0.5 * a - (1.0 / 54.0) * (c * r);
607 const t_point_type discr = p * p * p - q * q;
608 const fltp08 eps = getEPS();
609 t_point_type p_err = eps * ((3.0 / 3.0) * abs(b)
610 + (4.0 / 9.0) * (c * c)
611 + abs(p));
612 t_point_type r_err = eps * (6.0 * (c * c)
613 + 18.0 * abs(b)
614 + abs(r));
615 t_point_type q_err = 0.5 * abs(a) * eps
616 + (1.0 / 54.0) * abs(c) * (r_err + abs(r) * 3.0 * eps)
617 + abs(q) * eps;
618 t_point_type discr_err = (p * p) * (3.0 * p_err
619 + abs(p) * 2.0 * eps)
620 + abs(q) * (2.0 * q_err
621 + abs(q) * eps)
622 + abs(discr) * eps;
623
624 Buffer<t_point_type> z;// roots of the depressed cubic
625 // depending on the discriminant we get different solutions
626 if (abs(discr) <= discr_err)
627 {
628 // discriminant zero: one or two real roots
629 if (abs(p) <= p_err)
630 {
631 // p and q are zero: single root
632 z.resize(1);
633 z[0] = 0.0; // triple root
634 }
635 else
636 {
637 z.resize(2);
638 z[0] = 2.0 * q / p; // single root
639 z[1] = -0.5 * z[0]; // fltp08 root
640 }
641 }
642 else if (discr > Vertex < 3, fltp08>(0))
643 {
644 // three real roots: via trigonometric solution
645 z.resize(3);
646 for (uint01 n = 0; n < t_point_type::NumberOfDimensions(); n++)
647 {
648 fltp08 ac = (1.0 / 3.0) * acos(q[n] / (p[n] * sqrt(p[n])));
649 fltp08 sq = 2.0 * sqrt(p[n]);
650 z[0][n] = sq * cos(ac);
651 z[1][n] = sq * cos(ac - 2.0 * PI<fltp08>() / 3.0);
652 z[2][n] = sq * cos(ac - 4.0 * PI<fltp08>() / 3.0);
653 }
654 }
655 else if (discr < 0.0)
656 {
657 // single real root: via Cardano's fromula
658 z.resize(1);
659 for (uint01 n = 0; n < t_point_type::NumberOfDimensions(); n++)
660 {
661 fltp08 sgnq = (q[n] >= 0 ? 1 : -1);
662 fltp08 basis = abs(q[n]) + sqrt(-discr[n]);
663 fltp08 C = sgnq * pow(basis, 1.0 / 3.0); // c++11 has std::cbrt()
664 z[0][n] = C + p[n] / C;
665 }
666 }
667 for (uint04 i = 0; i < z.size(); i++)
668 {
669 // convert depressed cubic roots to original cubic: x = z - c/3
670 z[i] -= (1.0 / 3.0) * c;
671 // improve solution via newton steps
672 for (int k = 0; k < newton_iter; k++)
673 {
674 t_point_type f = ((z[i] + c) * z[i] + b) * z[i] + a;
675 t_point_type f1 = (3.0 * z[i] + 2.0 * c) * z[i] + b;
676 // only adjust if slope is large enough
677 if (abs(f1) > 1e-8)
678 z[i] -= f / f1;
679 }
680 }
681 // ensure if a=0 we get exactly x=0 as root
682 // TODO: remove this fudge
683 if (a == 0.0)
684 {
685 lib_assert(z.size() > 0, "cubic should always have at least one root");
686 t_point_type xmin = abs(z[0]);
687 uint04 imin = 0U;
688 for (uint04 i = 1; i < z.size(); i++)
689 {
690 if (xmin > abs(z[i]))
691 {
692 xmin = abs(z[i]);
693 imin = i;
694 }
695 }
696 z[imin] = 0.0; // replace the smallest absolute value with 0
697 }
698 std::sort(z.begin(), z.end());
699 return z;
700 }
701 void setBoundary(SplineBoundaryType left, fltp08 left_value, SplineBoundaryType right, fltp08 right_value)
702 {
703 lib_assert(m_x.size() == 0, "setPoints() must not have happened yet");
704 m_left = left;
705 m_right = right;
706 m_left_value = left_value;
707 m_right_value = right_value;
708 }
709 protected:
711 {
712 lib_assert(m_x.size() == m_y.size(), "m_y size");
713 lib_assert(m_x.size() == m_b.size(), "m_b size");
714 lib_assert(m_x.size() > 2, "size bust be > 2");
715 uint04 n = m_b.size();
716 if (m_c.size() != n)
717 m_c.resize(n);
718 if (m_d.size() != n)
719 m_d.resize(n);
720
721 for (uint04 i = 0; i < n - 1; i++)
722 {
723 const fltp08 h = m_x[i + 1] - m_x[i];
724 // from continuity and differentiability condition
725 m_c[i] = (3.0 * (m_y[i + 1] - m_y[i]) / h - (2.0 * m_b[i] + m_b[i + 1])) / h;
726 // from differentiability condition
727 m_d[i] = ((m_b[i + 1] - m_b[i]) / (3.0 * h) - 2.0 / 3.0 * m_c[i]) / h;
728 }
729
730 // for left extrapolation coefficients
731 m_c0 = (m_left == SplineBoundaryType::e_first_deriv) ? t_point_type(0.0) : m_c[0];
732 }
733 };
734
735}
#define lib_assert(expression, message)
Definition LibAssert.h:61
Band Matrix solver.
Definition BandMatrix.hpp:12
The equivelent of std::vector but with a bit more control. The basic array unit of the library.
Definition Buffer.hpp:56
void add(t_type &&object)
Adds object to the end of the buffer.
Definition Buffer.hpp:186
constexpr t_index_type size() const
Definition Buffer.hpp:823
decltype(auto) last()
Definition Buffer.hpp:588
decltype(auto) end()
Definition Buffer.hpp:507
void setSize(t_index_type new_size)
Definition Buffer.hpp:803
decltype(auto) begin()
Definition Buffer.hpp:402
void resize(t_index_type new_size)
Definition Buffer.hpp:812
A spline is a function used to interpolate or smooth data. Splines are a series of polynomials joined...
Definition Spline.hpp:14
Spline(const Buffer< t_point_type > &Y, SplineType type=SplineType::e_cspline, SplineBoundaryType left=SplineBoundaryType::e_second_deriv, fltp08 left_value=0.0, SplineBoundaryType right=SplineBoundaryType::e_second_deriv, fltp08 right_value=0.0)
Definition Spline.hpp:37
static fltp08 getEPS()
Definition Spline.hpp:498
SplineBoundaryType m_right
Definition Spline.hpp:27
fltp08 m_left_value
Definition Spline.hpp:28
Buffer< t_point_type > solveCubic(t_point_type a, t_point_type b, t_point_type c, t_point_type d, int newton_iter) const
Definition Spline.hpp:588
Buffer< t_point_type > m_c
Definition Spline.hpp:22
t_point_type deriv(uint04 order, fltp08 x) const
Definition Spline.hpp:379
fltp08 m_right_value
Definition Spline.hpp:29
void setCoeffsFromB()
Definition Spline.hpp:710
SplineType m_type
Definition Spline.hpp:25
t_point_type operator()(fltp08 x) const
Definition Spline.hpp:349
static Buffer< t_point_type > solveQuadratic(t_point_type a, t_point_type b, t_point_type c, int newton_iter=0)
Definition Spline.hpp:532
bool makeMonotonic()
Definition Spline.hpp:284
void setPoints(const Buffer< fltp08 > &x, const Buffer< t_point_type > &y, SplineType type)
Definition Spline.hpp:74
Spline(const Buffer< fltp08 > &X, const Buffer< t_point_type > &Y, SplineType type=SplineType::e_cspline, bool make_monotonic=false, SplineBoundaryType left=SplineBoundaryType::e_second_deriv, fltp08 left_value=0.0, SplineBoundaryType right=SplineBoundaryType::e_second_deriv, fltp08 right_value=0.0)
Definition Spline.hpp:55
SplineBoundaryType m_left
Definition Spline.hpp:26
Buffer< t_point_type > m_d
Definition Spline.hpp:23
void setBoundary(SplineBoundaryType left, fltp08 left_value, SplineBoundaryType right, fltp08 right_value)
Definition Spline.hpp:701
Spline()
Definition Spline.hpp:34
Buffer< t_point_type > m_y
Definition Spline.hpp:17
uint04 find_closest(fltp08 x) const
Definition Spline.hpp:343
Buffer< t_point_type > solve(t_point_type y, bool ignore_extrapolation) const
Definition Spline.hpp:441
bool m_made_monotonic
Definition Spline.hpp:30
Buffer< t_point_type > m_b
Definition Spline.hpp:21
static Buffer< t_point_type > solveLinear(t_point_type a, t_point_type b)
Definition Spline.hpp:504
t_point_type m_c0
Definition Spline.hpp:24
Buffer< fltp08 > m_x
Definition Spline.hpp:16
Definition ACIColor.h:37
constexpr t_type getMax(const t_type &left, const t_type &right)
Finds the max of the given arguments using the > operator The only requirement is that t_type have > ...
Definition BaseFunctions.hpp:94
SplineType
Definition SplineEnums.h:7
uint8_t uint01
-Defines an alias representing a 1 byte, unsigned integer -Can represent exact integer values 0 throu...
Definition BaseValues.hpp:80
std::enable_if<!ObjectInfo< t_type >::Float, fltp08 >::type cos(const Angle< t_type > &angle)
Performs optimized cosine operation on the given angle using pre-computed lookup table for optimal sp...
Definition AngleFunctions.h:124
uint32_t uint04
-Defines an alias representing a 4 byte, unsigned integer -Can represent exact integer values 0 throu...
Definition BaseValues.hpp:96
t_type sqrt(const t_type &value)
Definition VectorFunctions.hpp:1225
constexpr t_to cast(const Angle< t_from > &value)
Definition Angle.h:375
constexpr Angle< t_angle_type > abs(const Angle< t_angle_type > &value)
Changes an input with a negative sign, to a positive sign.
Definition AngleFunctions.h:645
@ A
Definition BaseValues.hpp:168
@ Y
Definition BaseValues.hpp:169
@ X
Definition BaseValues.hpp:167
@ C
Definition BaseValues.hpp:172
double fltp08
Defines an alias representing an 8 byte floating-point number.
Definition BaseValues.hpp:149
SplineBoundaryType
Definition SplineEnums.h:15
constexpr t_type getMin(const t_type &left, const t_type &right)
Finds the minimum of the given arguments based on the < operator Author: Tyler Parke Date: 2017-11-05...
Definition BaseFunctions.hpp:56