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