Easy3D 2.5.3
spline_interpolation.h
1/********************************************************************
2 * Copyright (C) 2015 Liangliang Nan <liangliang.nan@gmail.com>
3 * https://3d.bk.tudelft.nl/liangliang/
4 *
5 * This file is part of Easy3D. If it is useful in your research/work,
6 * I would be grateful if you show your appreciation by citing it:
7 * ------------------------------------------------------------------
8 * Liangliang Nan.
9 * Easy3D: a lightweight, easy-to-use, and efficient C++ library
10 * for processing and rendering 3D data.
11 * Journal of Open Source Software, 6(64), 3255, 2021.
12 * ------------------------------------------------------------------
13 *
14 * Easy3D is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License Version 3
16 * as published by the Free Software Foundation.
17 *
18 * Easy3D is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ********************************************************************/
26
27#ifndef EASY3D_CORE_SPLINE_INTERPOLATION_H
28#define EASY3D_CORE_SPLINE_INTERPOLATION_H
29
30#include <cstdio>
31#include <cassert>
32#include <vector>
33#include <algorithm>
34
35#include <easy3d/util/logging.h>
36
37
38namespace easy3d {
39
84 template<typename FT>
86 public:
87 enum BoundaryType {
88 first_deriv = 1,
89 second_deriv = 2
90 };
91
92 public:
95 SplineInterpolation() : left_(second_deriv), right_(second_deriv),
96 left_value_(0), right_value_(0),
97 linear_extrapolation_(false) {
98 }
99
102 void set_boundary(BoundaryType left, FT left_value,
103 BoundaryType right, FT right_value,
104 bool linear_extrapolation = false);
105
109 void set_data(const std::vector <FT> &x, const std::vector <FT> &y, bool cubic_spline = true);
110
112 FT operator()(FT x) const;
113
115 FT derivative(int order, FT x) const;
116
117 private:
118 std::vector <FT> x_, y_; // x,y coordinates of points
119 // interpolation parameters
120 // f(x) = a*(x-x_i)^3 + b*(x-x_i)^2 + c*(x-x_i) + y_i
121 std::vector <FT> a_, b_, c_; // spline coefficients
122 FT b0_, c0_; // for left extrapolation
123 BoundaryType left_, right_;
124 FT left_value_, right_value_;
125 bool linear_extrapolation_;
126 };
127
128
129
130 // \cond
137 template<typename FT>
138 class BandMatrix {
139 public:
141 BandMatrix() = default;
143 BandMatrix(int dim, int n_u, int n_l);
145 ~BandMatrix() = default;
146
147 void resize(int dim, int n_u, int n_l); // init with dim,n_u,n_l
148 int dim() const; // matrix dimension
149
150 int num_upper() const { return m_upper.size() - 1; }
151 int num_lower() const { return m_lower.size() - 1; }
152
154 FT &operator()(int i, int j); // write
156 const FT &operator()(int i, int j) const; // read
157
159 FT &saved_diag(int i);
160 const FT &saved_diag(int i) const;
161
163 void lu_decompose();
164
166 std::vector <FT> r_solve(const std::vector <FT> &b) const;
168 std::vector <FT> l_solve(const std::vector <FT> &b) const;
169 std::vector <FT> lu_solve(const std::vector <FT> &b, bool is_lu_decomposed = false);
170
171 private:
172 std::vector <std::vector<FT>> m_upper; // upper band
173 std::vector <std::vector<FT>> m_lower; // lower band
174
175 }; // BandMatrix
176 // \endcond
177
178
179 // Cubic spline implementation
180 // -----------------------
181
182 template<typename FT>
183 void SplineInterpolation<FT>::set_boundary(SplineInterpolation<FT>::BoundaryType left, FT left_value,
184 SplineInterpolation<FT>::BoundaryType right, FT right_value,
185 bool linear_extrapolation) {
186 assert(x_.size() == 0); // set_points() must not have happened yet
187 left_ = left;
188 right_ = right;
189 left_value_ = left_value;
190 right_value_ = right_value;
191 linear_extrapolation_ = linear_extrapolation;
192 }
193
194 template<typename FT>
195 void SplineInterpolation<FT>::set_data(const std::vector <FT> &x, const std::vector <FT> &y, bool cubic_spline) {
196 if (x.size() != y.size()) {
197 LOG(ERROR) << "sizes of x (" << x.size() << ") and y (" << y.size() << ") do not match";
198 return;
199 }
200 if (x.size() < 3) {
201 LOG(ERROR) << "too few data (size of x: " << x.size() << ")";
202 return;
203 }
204 x_ = x;
205 y_ = y;
206 const int n = static_cast<int>(x.size());
207 // TODO: maybe sort x and y, rather than returning an error
208 for (std::size_t i = 0; i < n - 1; i++) {
209 if (x_[i] >= x_[i + 1]) {
210 LOG_N_TIMES(3, ERROR) << "x has to be monotonously increasing (x[" << i << "]=" << x_[i] << ", x[" << i + 1 << "]=" << x_[i + 1] << "). " << COUNTER;
211 return;
212 }
213 }
214
215 if (cubic_spline) { // cubic spline interpolation
216 // setting up the matrix and right-hand side of the equation system
217 // for the parameters b[]
218 BandMatrix<FT> A(n, 1, 1);
219 std::vector <FT> rhs(n);
220 for (int i = 1; i < n - 1; i++) {
221 A(i, i - 1) = FT(1.0 / 3.0) * (x[i] - x[i - 1]);
222 A(i, i) = FT(2.0 / 3.0) * (x[i + 1] - x[i - 1]);
223 A(i, i + 1) = FT(1.0 / 3.0) * (x[i + 1] - x[i]);
224 rhs[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]) - (y[i] - y[i - 1]) / (x[i] - x[i - 1]);
225 }
226 // boundary conditions
228 // 2*b[0] = f''
229 A(0, 0) = FT(2.0);
230 A(0, 1) = FT(0.0);
231 rhs[0] = left_value_;
232 } else if (left_ == SplineInterpolation<FT>::first_deriv) {
233 // c[0] = f', needs to be re-expressed in terms of b:
234 // (2b[0]+b[1])(x[1]-x[0]) = 3 ((y[1]-y[0])/(x[1]-x[0]) - f')
235 A(0, 0) = FT(2.0) * (x[1] - x[0]);
236 A(0, 1) = FT(1.0) * (x[1] - x[0]);
237 rhs[0] = FT(3.0) * ((y[1] - y[0]) / (x[1] - x[0]) - left_value_);
238 } else {
239 assert(false);
240 }
242 // 2*b[n-1] = f''
243 A(n - 1, n - 1) = FT(2.0);
244 A(n - 1, n - 2) = FT(0.0);
245 rhs[n - 1] = right_value_;
246 } else if (right_ == SplineInterpolation<FT>::first_deriv) {
247 // c[n-1] = f', needs to be re-expressed in terms of b:
248 // (b[n-2]+2b[n-1])(x[n-1]-x[n-2])
249 // = 3 (f' - (y[n-1]-y[n-2])/(x[n-1]-x[n-2]))
250 A(n - 1, n - 1) = FT(2.0) * (x[n - 1] - x[n - 2]);
251 A(n - 1, n - 2) = FT(1.0) * (x[n - 1] - x[n - 2]);
252 rhs[n - 1] = FT(3.0) * (right_value_ - (y[n - 1] - y[n - 2]) / (x[n - 1] - x[n - 2]));
253 } else {
254 assert(false);
255 }
256
257 // solve the equation system to obtain the parameters b[]
258 b_ = A.lu_solve(rhs);
259
260 // calculate parameters a[] and c[] based on b[]
261 a_.resize(n);
262 c_.resize(n);
263 for (std::size_t i = 0; i < n - 1; i++) {
264 a_[i] = FT(1.0 / 3.0) * (b_[i + 1] - b_[i]) / (x[i + 1] - x[i]);
265 c_[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i])
266 - FT(1.0 / 3.0) * (FT(2.0) * b_[i] + b_[i + 1]) * (x[i + 1] - x[i]);
267 }
268 } else { // linear interpolation
269 a_.resize(n);
270 b_.resize(n);
271 c_.resize(n);
272 for (std::size_t i = 0; i < n - 1; i++) {
273 a_[i] = FT(0.0);
274 b_[i] = FT(0.0);
275 c_[i] = (y_[i + 1] - y_[i]) / (x_[i + 1] - x_[i]);
276 }
277 }
278
279 // for left extrapolation coefficients
280 b0_ = linear_extrapolation_ ? FT(0.0) : b_[0];
281 c0_ = c_[0];
282
283 // for the right extrapolation coefficients
284 // f_{n-1}(x) = b*(x-x_{n-1})^2 + c*(x-x_{n-1}) + y_{n-1}
285 FT h = x[n - 1] - x[n - 2];
286 // b_[n-1] is determined by the boundary condition
287 a_[n - 1] = FT(0.0);
288 c_[n - 1] = FT(3.0) * a_[n - 2] * h * h + FT(2.0) * b_[n - 2] * h + c_[n - 2]; // = f'_{n-2}(x_{n-1})
289 if (linear_extrapolation_)
290 b_[n - 1] = FT(0.0);
291 }
292
293 template<typename FT>
295 size_t n = x_.size();
296 // find the closest point x_[idx] < x, idx=0 even if x<x_[0]
297 typename std::vector<FT>::const_iterator it;
298 it = std::lower_bound(x_.begin(), x_.end(), x);
299 int idx = std::max(int(it - x_.begin()) - 1, 0);
300
301 FT h = x - x_[idx];
302 FT interpol;
303 if (x < x_[0]) {
304 // extrapolation to the left
305 interpol = (b0_ * h + c0_) * h + y_[0];
306 } else if (x > x_[n - 1]) {
307 // extrapolation to the right
308 interpol = (b_[n - 1] * h + c_[n - 1]) * h + y_[n - 1];
309 } else {
310 // interpolation
311 interpol = ((a_[idx] * h + b_[idx]) * h + c_[idx]) * h + y_[idx];
312 }
313 return interpol;
314 }
315
316 template<typename FT>
317 FT SplineInterpolation<FT>::derivative(int order, FT x) const {
318 assert(order > 0);
319
320 size_t n = x_.size();
321 // find the closest point x_[idx] < x, idx=0 even if x<x_[0]
322 typename std::vector<FT>::const_iterator it;
323 it = std::lower_bound(x_.begin(), x_.end(), x);
324 int idx = std::max(int(it - x_.begin()) - 1, 0);
325
326 FT h = x - x_[idx];
327 FT interpol;
328 if (x < x_[0]) {
329 // extrapolation to the left
330 switch (order) {
331 case 1:
332 interpol = FT(2.0) * b0_ * h + c0_;
333 break;
334 case 2:
335 interpol = FT(2.0) * b0_ * h;
336 break;
337 default:
338 interpol = FT(0.0);
339 break;
340 }
341 } else if (x > x_[n - 1]) {
342 // extrapolation to the right
343 switch (order) {
344 case 1:
345 interpol = FT(2.0) * b_[n - 1] * h + c_[n - 1];
346 break;
347 case 2:
348 interpol = FT(2.0) * b_[n - 1];
349 break;
350 default:
351 interpol = FT(0.0);
352 break;
353 }
354 } else {
355 // interpolation
356 switch (order) {
357 case 1:
358 interpol = (FT(3.0) * a_[idx] * h + FT(2.0) * b_[idx]) * h + c_[idx];
359 break;
360 case 2:
361 interpol = FT(6.0) * a_[idx] * h + FT(2.0) * b_[idx];
362 break;
363 case 3:
364 interpol = FT(6.0) * a_[idx];
365 break;
366 default:
367 interpol = FT(0.0);
368 break;
369 }
370 }
371 return interpol;
372 }
373
374
375 // \cond
376 // BandMatrix implementation
377 // -------------------------
378
379 template<typename FT>
380 BandMatrix<FT>::BandMatrix(int dim, int n_u, int n_l) {
381 resize(dim, n_u, n_l);
382 }
383
384 template<typename FT>
385 void BandMatrix<FT>::resize(int dim, int n_u, int n_l) {
386 assert(dim > 0);
387 assert(n_u >= 0);
388 assert(n_l >= 0);
389 m_upper.resize(n_u + 1);
390 m_lower.resize(n_l + 1);
391 for (size_t i = 0; i < m_upper.size(); i++) {
392 m_upper[i].resize(dim);
393 }
394 for (size_t i = 0; i < m_lower.size(); i++) {
395 m_lower[i].resize(dim);
396 }
397 }
398
399 template<typename FT>
400 int BandMatrix<FT>::dim() const {
401 if (m_upper.size() > 0) {
402 return m_upper[0].size();
403 } else {
404 return 0;
405 }
406 }
407
408
409 // defines the new operator (), so that we can access the elements
410 // by A(i,j), index going from i=0,...,dim()-1
411 template<typename FT>
412 FT &BandMatrix<FT>::operator()(int i, int j) {
413 int k = j - i; // what band is the entry
414 assert((i >= 0) && (i < dim()) && (j >= 0) && (j < dim()));
415 assert((-num_lower() <= k) && (k <= num_upper()));
416 // k=0 -> diagonal, k<0 lower left part, k>0 upper right part
417 if (k >= 0) return m_upper[k][i];
418 else return m_lower[-k][i];
419 }
420
421 template<typename FT>
422 const FT &BandMatrix<FT>::operator()(int i, int j) const {
423 int k = j - i; // what band is the entry
424 assert((i >= 0) && (i < dim()) && (j >= 0) && (j < dim()));
425 assert((-num_lower() <= k) && (k <= num_upper()));
426 // k=0 -> diagonal, k<0 lower left part, k>0 upper right part
427 if (k >= 0) return m_upper[k][i];
428 else return m_lower[-k][i];
429 }
430
431 // second diag (used in LU decomposition), saved in m_lower
432 template<typename FT>
433 const FT &BandMatrix<FT>::saved_diag(int i) const {
434 assert((i >= 0) && (i < dim()));
435 return m_lower[0][i];
436 }
437
438 template<typename FT>
439 FT &BandMatrix<FT>::saved_diag(int i) {
440 assert((i >= 0) && (i < dim()));
441 return m_lower[0][i];
442 }
443
444 // LR-Decomposition of a band matrix
445 template<typename FT>
446 void BandMatrix<FT>::lu_decompose() {
447 int i_max, j_max;
448 int j_min;
449 FT x;
450
451 // preconditioning
452 // normalize column i so that a_ii=1
453 for (int i = 0; i < this->dim(); i++) {
454 assert(this->operator()(i, i) != 0.0);
455 this->saved_diag(i) = FT(1.0) / this->operator()(i, i);
456 j_min = std::max(0, i - this->num_lower());
457 j_max = std::min(this->dim() - 1, i + this->num_upper());
458 for (int j = j_min; j <= j_max; j++) {
459 this->operator()(i, j) *= this->saved_diag(i);
460 }
461 this->operator()(i, i) = FT(1.0); // prevents rounding errors
462 }
463
464 // Gauss LR-Decomposition
465 for (int k = 0; k < this->dim(); k++) {
466 i_max = std::min(this->dim() - 1, k + this->num_lower()); // num_lower not a mistake!
467 for (int i = k + 1; i <= i_max; i++) {
468 assert(this->operator()(k, k) != FT(0.0));
469 x = -this->operator()(i, k) / this->operator()(k, k);
470 this->operator()(i, k) = -x; // assembly part of L
471 j_max = std::min(this->dim() - 1, k + this->num_upper());
472 for (int j = k + 1; j <= j_max; j++) {
473 // assembly part of R
474 this->operator()(i, j) = this->operator()(i, j) + x * this->operator()(k, j);
475 }
476 }
477 }
478 }
479
480 // solves Ly=b
481 template<typename FT>
482 std::vector <FT> BandMatrix<FT>::l_solve(const std::vector <FT> &b) const {
483 assert(this->dim() == (int) b.size());
484 std::vector <FT> x(this->dim());
485 int j_start;
486 FT sum;
487 for (int i = 0; i < this->dim(); i++) {
488 sum = FT(0);
489 j_start = std::max(0, i - this->num_lower());
490 for (int j = j_start; j < i; j++) sum += this->operator()(i, j) * x[j];
491 x[i] = (b[i] * this->saved_diag(i)) - sum;
492 }
493 return x;
494 }
495
496 // solves Rx=y
497 template<typename FT>
498 std::vector <FT> BandMatrix<FT>::r_solve(const std::vector <FT> &b) const {
499 assert(this->dim() == (int) b.size());
500 std::vector <FT> x(this->dim());
501 int j_stop;
502 FT sum;
503 for (int i = this->dim() - 1; i >= 0; i--) {
504 sum = 0;
505 j_stop = std::min(this->dim() - 1, i + this->num_upper());
506 for (int j = i + 1; j <= j_stop; j++) sum += this->operator()(i, j) * x[j];
507 x[i] = (b[i] - sum) / this->operator()(i, i);
508 }
509 return x;
510 }
511
512 template<typename FT>
513 std::vector <FT> BandMatrix<FT>::lu_solve(const std::vector <FT> &b, bool is_lu_decomposed) {
514 assert(this->dim() == (int) b.size());
515 std::vector <FT> x, y;
516 if (!is_lu_decomposed) {
517 this->lu_decompose();
518 }
519 y = this->l_solve(b);
520 x = this->r_solve(y);
521 return x;
522 }
523 // \endcond
524
525}
526
527
528#endif //EASY3D_CORE_SPLINE_INTERPOLATION_H
Cubic spline interpolation.
Definition: spline_interpolation.h:85
SplineInterpolation()
Definition: spline_interpolation.h:95
void set_data(const std::vector< FT > &x, const std::vector< FT > &y, bool cubic_spline=true)
Definition: spline_interpolation.h:195
FT operator()(FT x) const
Evaluates the spline at x.
Definition: spline_interpolation.h:294
FT derivative(int order, FT x) const
Returns the order -th derivative of the spline at x.
Definition: spline_interpolation.h:317
void set_boundary(BoundaryType left, FT left_value, BoundaryType right, FT right_value, bool linear_extrapolation=false)
Definition: spline_interpolation.h:183
Definition: collider.cpp:182
std::vector< FT > sum(const Matrix< FT > &)
Definition: matrix.h:1454