Theoretica
Mathematical Library
Loading...
Searching...
No Matches
splines.h
Go to the documentation of this file.
1
5
6#ifndef THEORETICA_SPLINES_H
7#define THEORETICA_SPLINES_H
8
9#include "../core/constants.h"
10#include "../algebra/algebra_types.h"
11
12
13namespace theoretica {
14
15
17 inline real lerp(real x1, real x2, real interp) {
18 return (x1 + interp * (x2 - x1));
19 }
20
21
23 template<unsigned int N>
24 inline vec<real, N> lerp(
25 const vec<real, N>& P1, const vec<real, N>& P2, real interp) {
26
27 return (P1 + (P2 - P1) * interp);
28 }
29
30
32 inline real invlerp(real x1, real x2, real value) {
33 return (value - x1) / (x2 - x1);
34 }
35
36
38 template<unsigned int N>
39 inline vec<real, N> invlerp(
40 const vec<real, N>& P1, const vec<real, N>& P2, real value) {
41
42 real t = invlerp(P1.get(0), P2.get(0), value);
43
44 // Check that all computed t_i are the same
45 for (int i = 1; i < N; ++i) {
46
47 real t_new = invlerp(P1.get(i), P2.get(i), value);
48
49 if(t != t_new) {
50 TH_MATH_ERROR("invlerp", t_new, MathError::OutOfDomain);
51 return nan();
52 }
53 }
54
55 return t;
56 }
57
58
60 inline real remap(real iFrom, real iTo, real oFrom, real oTo, real value) {
61 return lerp(oFrom, oTo, invlerp(iFrom, iTo, value));
62 }
63
64
66 template<unsigned int N>
67 inline vec<real, N> remap(
68 const vec<real, N>& iFrom, const vec<real, N>& iTo,
69 const vec<real, N>& oFrom, const vec<real, N>& oTo, real value) {
70 return lerp(oFrom, oTo, invlerp(iFrom, iTo, value));
71 }
72
73
75 template<unsigned int N>
76 inline vec<real, N> nlerp(
77 const vec<real, N>& P1, const vec<real, N>& P2, real interp) {
78
79 return (P1 + (P2 - P1) * interp).normalized();
80 }
81
82
84 template<unsigned int N>
85 inline vec<real, N> slerp(
86 const vec<real, N>& P1, const vec<real, N>& P2, real t) {
87
88 // Compute (only once) the length
89 // of the input vectors
90 const real P1_l = P1.norm();
91 const real P2_l = P2.norm();
92
93 // Check whether one of the vectors is null,
94 // which would make the computation impossible
95 if(P1_l == 0 || P2_l == 0) {
96 TH_MATH_ERROR("slerp", P1_l ? P2_l : P1_l, MathError::ImpossibleOperation);
97 return vec<real, N>(nan());
98 }
99
100 // Angle between P1 and P2 (from the dot product)
101 real omega = acos((P1 * P2) / (P1_l * P2_l));
102 real s = sin(omega);
103
104 // Check that the sine of the angle is not zero
105 if(s == 0) {
107 return vec<real, N>(nan());
108 }
109
110 return (P1 * sin((1 - t) * omega) + P2 * sin(t * omega)) / s;
111 }
112
113
114 // Sigmoid-like interpolation
115
116
118 inline real smoothstep(real x1, real x2, real interp) {
119
120 if(x1 == x2) {
121 TH_MATH_ERROR("smoothstep", x1, MathError::DivByZero);
122 return nan();
123 }
124
125 // Clamp x between 0 and 1
126 const real x = clamp((interp - x1) / (x2 - x1), 0.0, 1.0);
127
128 // 3x^2 - 2x^3
129 return x * x * (3 - 2 * x);
130 }
131
132
134 inline real smootherstep(real x1, real x2, real interp) {
135
136 if(x1 == x2) {
137 TH_MATH_ERROR("smootherstep", x1, MathError::DivByZero);
138 return nan();
139 }
140
141 // Clamp x between 0 and 1
142 const real x = clamp((interp - x1) / (x2 - x1), 0.0, 1.0);
143
144 // 6x^5 - 15x^4 + 10x^3
145 return x * x * x * (x * (x * 6 - 15) + 10);
146 }
147
148
149 // Bezier curves
150
151
153 template<unsigned int N>
154 inline vec<real, N> bezier_quadratic(
155 const vec<real, N>& P0, const vec<real, N>& P1,
156 const vec<real, N>& P2, real t) {
157
158 return lerp(lerp(P0, P1, t), lerp(P1, P2, t), t);
159 }
160
161
163 template<unsigned int N>
164 inline vec<real, N> bezier_cubic(
165 const vec<real, N>& P0, const vec<real, N>& P1,
166 const vec<real, N>& P2, vec<real, N> P3, real t) {
167
168 const vec<real, N> A = lerp(P0, P1, t);
169 const vec<real, N> B = lerp(P1, P2, t);
170 const vec<real, N> C = lerp(P2, P3, t);
171
172 const vec<real, N> D = lerp(A, B, t);
173 const vec<real, N> E = lerp(B, C, t);
174
175 return lerp(D, E, t);
176 }
177
178
180 struct spline_node {
181
184
187 real a, b, c, d;
188
190 spline_node() : x(0), a(0), b(0), c(0), d(0) {}
191
194 : x(x), a(a), b(b), c(c), d(d) {}
195
196
199 inline real operator()(real X) const {
200 const real h = X - x;
201 return a + h * (b + h * (c + h * d));
202 }
203
204
207 inline real deriv(real X) const {
208 const real h = X - x;
209 return b + h * (c * 2 + h * d * 3);
210 }
211 };
212
213
221 template<typename Dataset1, typename Dataset2>
222 inline std::vector<spline_node> splines_cubic(
223 const Dataset1& x, const Dataset2& y) {
224
225 if(x.size() < 2) {
226 TH_MATH_ERROR("splines_cubic", x.size(), MathError::InvalidArgument);
227 return { spline_node(nan(), nan(), nan(), nan(), nan()) };
228 }
229
230 if(x.size() != y.size()) {
231 TH_MATH_ERROR("splines_cubic", x.size(), MathError::InvalidArgument);
232 return { spline_node(nan(), nan(), nan(), nan(), nan()) };
233 }
234
235 const size_t n_points = x.size();
236 const size_t n_nodes = n_points - 1;
237
238 if(n_points < 2) {
239 TH_MATH_ERROR("splines_cubic", n_points, MathError::InvalidArgument);
240 return { spline_node(nan(), nan(), nan(), nan(), nan()) };
241 }
242
243 // Check for strictly increasing x values
244 for (size_t i = 0; i < n_nodes; ++i) {
245
246 if(x[i + 1] <= x[i]) {
247 TH_MATH_ERROR("splines_cubic", x[i + 1] - x[i], MathError::InvalidArgument);
248 return { spline_node(nan(), nan(), nan(), nan(), nan()) };
249 }
250 }
251
252 // Special case for linear interpolation
253 if(n_points == 2) {
254 const real slope = (y[1] - y[0]) / (x[1] - x[0]);
255 return { spline_node(x[0], y[0], slope, 0, 0) };
256 }
257
258 // Second derivatives at each point
259 std::vector<real> m (n_points);
260
261 // Intervals, reused for diagonal ratios
262 std::vector<real> h (n_nodes);
263
264 // Compute differences h[i] = x[i+1] - x[i]
265 for (size_t i = 0; i < n_nodes; ++i)
266 h[i] = x[i + 1] - x[i];
267
268 // Natural spline boundary conditions: m[0] = m[n] = 0
269 m[0] = 0;
270 m[n_nodes] = 0;
271
272 // Solve tridiagonal system using Thomas algorithm
273 // Am = b where m are the second derivatives,
274 // using in-place forward elimination,
275 // h array will store the modified diagonal ratios.
276
277 // Compute RHS and setup for Thomas algorithm
278 std::vector<real> rhs (n_points);
279 rhs[0] = 0;
280 rhs[n_nodes] = 0;
281
282 // Build RHS: 6 * [(y[i+1] - y[i]) / h[i] - (y[i] - y[i-1]) / h[i-1]]
283 for (size_t i = 1; i < n_nodes; ++i) {
284 rhs[i] = 6.0 * (
285 (y[i + 1] - y[i]) / h[i] -
286 (y[i] - y[i - 1]) / h[i - 1]
287 );
288 }
289
290 // Forward elimination
291
292 // Tridiagonal matrix has form:
293 // [ 2(h[0]+h[1]) h[1] 0, ... ]
294 // [ h[1] 2(h[1]+h[2]) h[2] ]
295 // [ 0 h[2] 2(h[2]+h[3]) ]
296 // [ 0 0 ... ... ]
297
298 // Diagonal elements
299 std::vector<real> diag (n_points);
300
301 // Boundary conditions
302 diag[0] = 1.0;
303 diag[n_nodes] = 1.0;
304
305 for (size_t i = 1; i < n_nodes; ++i)
306 diag[i] = 2.0 * (h[i - 1] + h[i]);
307
308 // Forward sweep
309 for (size_t i = 1; i < n_nodes; ++i) {
310 const real ratio = h[i - 1] / diag[i - 1];
311 diag[i] -= ratio * h[i - 1];
312 rhs[i] -= ratio * rhs[i - 1];
313 }
314
315 // Back substitution
316 for (int i = int(n_nodes) - 1; i >= 0; --i) {
317
318 if(i < int(n_nodes) - 1)
319 m[i] = (rhs[i] - h[i] * m[i + 1]) / diag[i];
320 else
321 m[i] = rhs[i] / diag[i];
322 }
323
324 // Compute spline coefficients from second derivatives
325 // For the interval [x[i], x[i+1]], the spline is:
326 // S(x) = a + b * (x - x[i]) + c * (x - x[i])^2 + d * (x - x[i])^3
327 std::vector<spline_node> nodes (n_nodes);
328 for (size_t i = 0; i < n_nodes; ++i) {
329
330 // Coefficients in terms of second derivatives m and differences h
331 const real a = y[i];
332 const real b = (y[i + 1] - y[i]) / h[i] - h[i] * (2.0 * m[i] + m[i + 1]) / 6.0;
333 const real c = m[i] * 0.5;
334 const real d = (m[i + 1] - m[i]) / (6.0 * h[i]);
335
336 nodes[i] = spline_node(x[i], a, b, c, d);
337 }
338
339 return nodes;
340 }
341
342
349 template <typename DataPoints = std::vector<vec2>>
350 inline std::vector<spline_node> splines_cubic(DataPoints p) {
351
352 // Wrap a vector of vectors, providing access to a single coordinate as a vector
353 struct accessor {
354
355 const DataPoints& points;
356 size_t j;
357
358 accessor(const DataPoints& points, size_t j) : points(points), j(j) {}
359 inline real operator[](size_t i) const { return points[i][j]; }
360 inline size_t size() const { return points.size(); }
361 };
362
363 return splines_cubic(accessor(p, 0), accessor(p, 1));
364 }
365
366
369 class spline {
370 public:
371
374 std::vector<spline_node> nodes;
375
376
381 template<typename DataPoints = std::vector<vec2>>
382 spline(const DataPoints& p) {
383 nodes = splines_cubic(p);
384 }
385
386
391 template<typename Dataset1, typename Dataset2>
392 spline(const Dataset1& X, const Dataset2& Y) {
393 nodes = splines_cubic(X, Y);
394 }
395
396
398 inline spline& operator=(const spline& other) {
399 nodes = other.nodes;
400 return *this;
401 }
402
403
406 inline real operator()(real x) const {
407
408 for (int i = int(nodes.size()) - 1; i > 0; --i)
409 if(x >= nodes[i].x)
410 return nodes[i](x);
411
412 // Extrapolation for x < x_0
413 return nodes[0](x);
414 }
415
416
419 inline real deriv(real x) const {
420
421 for (unsigned int i = nodes.size() - 1; i > 0; --i)
422 if(x >= nodes[i].x)
423 return nodes[i].deriv(x);
424
425 // Extrapolation for x < x_0
426 return nodes[0].deriv(x);
427 }
428
429
431 inline auto begin() {
432 return nodes.begin();
433 }
434
435
437 inline auto end() {
438 return nodes.end();
439 }
440 };
441
442
443}
444
445#endif
representing a ratio between two objects, like a fraction or a rational polynomial.
Definition ratio.h:23
A natural cubic spline interpolation class.
Definition splines.h:369
spline(const Dataset1 &X, const Dataset2 &Y)
Construct the natural cubic spline interpolation from the sets of x[i] and y[i] data points.
Definition splines.h:392
real operator()(real x) const
Evaluate the natural cubic spline interpolation at a given point.
Definition splines.h:406
real deriv(real x) const
Evaluate the derivative of the natural cubic spline interpolation at a given point.
Definition splines.h:419
auto end()
Get an iterator to one plus the last spline element.
Definition splines.h:437
auto begin()
Get an iterator to the first spline element.
Definition splines.h:431
spline(const DataPoints &p)
Construct the natural cubic spline interpolation from a vector of coordinate pairs.
Definition splines.h:382
spline & operator=(const spline &other)
Copy from another spline.
Definition splines.h:398
std::vector< spline_node > nodes
The computed nodes of the natural cubic spline interpolation over the points.
Definition splines.h:374
#define TH_MATH_ERROR(F_NAME, VALUE, EXCEPTION)
TH_MATH_ERROR is a macro which throws exceptions or modifies errno (depending on which compilation op...
Definition error.h:238
Main namespace of the library which contains all functions and objects.
Definition algebra.h:27
double real
A real number, defined as a floating point type.
Definition constants.h:207
real smootherstep(real x1, real x2, real interp)
Smootherstep interpolation.
Definition splines.h:134
vec< real, N > nlerp(const vec< real, N > &P1, const vec< real, N > &P2, real interp)
Normalized linear interpolation.
Definition splines.h:76
real remap(real iFrom, real iTo, real oFrom, real oTo, real value)
Remap a value from one range to another.
Definition splines.h:60
real smoothstep(real x1, real x2, real interp)
Smoothstep interpolation.
Definition splines.h:118
vec< real, N > bezier_quadratic(const vec< real, N > &P0, const vec< real, N > &P1, const vec< real, N > &P2, real t)
Quadratic Bezier curve.
Definition splines.h:154
real invlerp(real x1, real x2, real value)
Inverse linear interpolation.
Definition splines.h:32
TH_CONSTEXPR real nan()
Return a quiet NaN number in floating point representation.
Definition error.h:78
@ InvalidArgument
Invalid argument.
@ OutOfDomain
Argument out of domain.
@ ImpossibleOperation
Mathematically impossible operation.
@ DivByZero
Division by zero.
real lerp(real x1, real x2, real interp)
Linear interpolation.
Definition splines.h:17
constexpr real E
The Euler mathematical constant (e)
Definition constants.h:246
vec< real, N > bezier_cubic(const vec< real, N > &P0, const vec< real, N > &P1, const vec< real, N > &P2, vec< real, N > P3, real t)
Cubic Bezier curve.
Definition splines.h:164
dual2 acos(dual2 x)
Compute the arcosine of a second order dual number.
Definition dual2_functions.h:223
vec< real, N > slerp(const vec< real, N > &P1, const vec< real, N > &P2, real t)
Spherical interpolation.
Definition splines.h:85
dual2 sin(dual2 x)
Compute the sine of a second order dual number.
Definition dual2_functions.h:72
std::vector< spline_node > splines_cubic(const Dataset1 &x, const Dataset2 &y)
Compute the cubic splines interpolation of a set of data points using an optimized Thomas algorithm,...
Definition splines.h:222
real clamp(real x, real a, real b)
Clamp x between a and b.
Definition real_analysis.h:355
A cubic splines node for a given x interval.
Definition splines.h:180
spline_node()
Default constructor.
Definition splines.h:190
real operator()(real X) const
Evaluate the interpolating cubic spline (no check on the input value is performed!...
Definition splines.h:199
real deriv(real X) const
Evaluate the derivative of the interpolating cubic spline (no check on the input value is performed)
Definition splines.h:207
real x
Upper extreme of the interpolation interval .
Definition splines.h:183
real a
Coefficients of the interpolating cubic spline .
Definition splines.h:187
spline_node(real x, real a, real b, real c, real d)
Construct from and polynomial coefficients.
Definition splines.h:193