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