spline.h
1 /*
2  * spline.h
3  *
4  * simple cubic spline interpolation library without external
5  * dependencies
6  *
7  * ---------------------------------------------------------------------
8  * Copyright (C) 2011, 2014 Tino Kluge (ttk448 at gmail.com)
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program. If not, see <http://www.gnu.org/licenses/>.
22  * ---------------------------------------------------------------------
23  *
24  *
25  * MS: download from http://kluge.in-chemnitz.de/opensource/spline/ on Feb 20 2018
26  */
27 
28 
29 #ifndef TK_SPLINE_H
30 #define TK_SPLINE_H
31 
32 #include <cstdio>
33 #include <cassert>
34 #include <vector>
35 #include <algorithm>
36 
37 
38 // unnamed namespace only because the implementation is in this
39 // header file and we don't want to export symbols to the obj files
40 namespace
41 {
42 
43 namespace tk
44 {
45 
46 // band matrix solver
47 class band_matrix
48 {
49 private:
50  std::vector< std::vector<double> > m_upper; // upper band
51  std::vector< std::vector<double> > m_lower; // lower band
52 public:
53  band_matrix() {}; // constructor
54  band_matrix(int dim, int n_u, int n_l); // constructor
55  ~band_matrix() {}; // destructor
56  void resize(int dim, int n_u, int n_l); // init with dim,n_u,n_l
57  int dim() const; // matrix dimension
58  int num_upper() const
59  {
60  return m_upper.size()-1;
61  }
62  int num_lower() const
63  {
64  return m_lower.size()-1;
65  }
66  // access operator
67  double & operator () (int i, int j); // write
68  double operator () (int i, int j) const; // read
69  // we can store an additional diogonal (in m_lower)
70  double& saved_diag(int i);
71  double saved_diag(int i) const;
72  void lu_decompose();
73  std::vector<double> r_solve(const std::vector<double>& b) const;
74  std::vector<double> l_solve(const std::vector<double>& b) const;
75  std::vector<double> lu_solve(const std::vector<double>& b,
76  bool is_lu_decomposed=false);
77 
78 };
79 
80 
81 // spline interpolation
82 class spline
83 {
84 public:
85  enum bd_type {
86  first_deriv = 1,
87  second_deriv = 2
88  };
89 
90 private:
91  std::vector<double> m_x,m_y; // x,y coordinates of points
92  // interpolation parameters
93  // f(x) = a*(x-x_i)^3 + b*(x-x_i)^2 + c*(x-x_i) + y_i
94  std::vector<double> m_a,m_b,m_c; // spline coefficients
95  double m_b0, m_c0; // for left extrapol
96  bd_type m_left, m_right;
97  double m_left_value, m_right_value;
98  bool m_force_linear_extrapolation;
99 
100 public:
101  // set default boundary condition to be zero curvature at both ends
102  spline(): m_left(second_deriv), m_right(second_deriv),
103  m_left_value(0.0), m_right_value(0.0),
104  m_force_linear_extrapolation(false)
105  {
106  ;
107  }
108 
109  // optional, but if called it has to come be before set_points()
110  void set_boundary(bd_type left, double left_value,
111  bd_type right, double right_value,
112  bool force_linear_extrapolation=false);
113  void set_points(const std::vector<double>& x,
114  const std::vector<double>& y, bool cubic_spline=true);
115  double operator() (double x) const;
116 };
117 
118 
119 
120 // ---------------------------------------------------------------------
121 // implementation part, which could be separated into a cpp file
122 // ---------------------------------------------------------------------
123 
124 
125 // band_matrix implementation
126 // -------------------------
127 
128 band_matrix::band_matrix(int dim, int n_u, int n_l)
129 {
130  resize(dim, n_u, n_l);
131 }
132 void band_matrix::resize(int dim, int n_u, int n_l)
133 {
134  assert(dim>0);
135  assert(n_u>=0);
136  assert(n_l>=0);
137  m_upper.resize(n_u+1);
138  m_lower.resize(n_l+1);
139  for(size_t i=0; i<m_upper.size(); i++) {
140  m_upper[i].resize(dim);
141  }
142  for(size_t i=0; i<m_lower.size(); i++) {
143  m_lower[i].resize(dim);
144  }
145 }
146 int band_matrix::dim() const
147 {
148  if(m_upper.size()>0) {
149  return m_upper[0].size();
150  } else {
151  return 0;
152  }
153 }
154 
155 
156 // defines the new operator (), so that we can access the elements
157 // by A(i,j), index going from i=0,...,dim()-1
158 double & band_matrix::operator () (int i, int j)
159 {
160  int k=j-i; // what band is the entry
161  assert( (i>=0) && (i<dim()) && (j>=0) && (j<dim()) );
162  assert( (-num_lower()<=k) && (k<=num_upper()) );
163  // k=0 -> diogonal, k<0 lower left part, k>0 upper right part
164  if(k>=0) return m_upper[k][i];
165  else return m_lower[-k][i];
166 }
167 double band_matrix::operator () (int i, int j) const
168 {
169  int k=j-i; // what band is the entry
170  assert( (i>=0) && (i<dim()) && (j>=0) && (j<dim()) );
171  assert( (-num_lower()<=k) && (k<=num_upper()) );
172  // k=0 -> diogonal, k<0 lower left part, k>0 upper right part
173  if(k>=0) return m_upper[k][i];
174  else return m_lower[-k][i];
175 }
176 // second diag (used in LU decomposition), saved in m_lower
177 double band_matrix::saved_diag(int i) const
178 {
179  assert( (i>=0) && (i<dim()) );
180  return m_lower[0][i];
181 }
182 double & band_matrix::saved_diag(int i)
183 {
184  assert( (i>=0) && (i<dim()) );
185  return m_lower[0][i];
186 }
187 
188 // LR-Decomposition of a band matrix
189 void band_matrix::lu_decompose()
190 {
191  int i_max,j_max;
192  int j_min;
193  double x;
194 
195  // preconditioning
196  // normalize column i so that a_ii=1
197  for(int i=0; i<this->dim(); i++) {
198  assert(this->operator()(i,i)!=0.0);
199  this->saved_diag(i)=1.0/this->operator()(i,i);
200  j_min=std::max(0,i-this->num_lower());
201  j_max=std::min(this->dim()-1,i+this->num_upper());
202  for(int j=j_min; j<=j_max; j++) {
203  this->operator()(i,j) *= this->saved_diag(i);
204  }
205  this->operator()(i,i)=1.0; // prevents rounding errors
206  }
207 
208  // Gauss LR-Decomposition
209  for(int k=0; k<this->dim(); k++) {
210  i_max=std::min(this->dim()-1,k+this->num_lower()); // num_lower not a mistake!
211  for(int i=k+1; i<=i_max; i++) {
212  assert(this->operator()(k,k)!=0.0);
213  x=-this->operator()(i,k)/this->operator()(k,k);
214  this->operator()(i,k)=-x; // assembly part of L
215  j_max=std::min(this->dim()-1,k+this->num_upper());
216  for(int j=k+1; j<=j_max; j++) {
217  // assembly part of R
218  this->operator()(i,j)=this->operator()(i,j)+x*this->operator()(k,j);
219  }
220  }
221  }
222 }
223 // solves Ly=b
224 std::vector<double> band_matrix::l_solve(const std::vector<double>& b) const
225 {
226  assert( this->dim()==(int)b.size() );
227  std::vector<double> x(this->dim());
228  int j_start;
229  double sum;
230  for(int i=0; i<this->dim(); i++) {
231  sum=0;
232  j_start=std::max(0,i-this->num_lower());
233  for(int j=j_start; j<i; j++) sum += this->operator()(i,j)*x[j];
234  x[i]=(b[i]*this->saved_diag(i)) - sum;
235  }
236  return x;
237 }
238 // solves Rx=y
239 std::vector<double> band_matrix::r_solve(const std::vector<double>& b) const
240 {
241  assert( this->dim()==(int)b.size() );
242  std::vector<double> x(this->dim());
243  int j_stop;
244  double sum;
245  for(int i=this->dim()-1; i>=0; i--) {
246  sum=0;
247  j_stop=std::min(this->dim()-1,i+this->num_upper());
248  for(int j=i+1; j<=j_stop; j++) sum += this->operator()(i,j)*x[j];
249  x[i]=( b[i] - sum ) / this->operator()(i,i);
250  }
251  return x;
252 }
253 
254 std::vector<double> band_matrix::lu_solve(const std::vector<double>& b,
255  bool is_lu_decomposed)
256 {
257  assert( this->dim()==(int)b.size() );
258  std::vector<double> x,y;
259  if(is_lu_decomposed==false) {
260  this->lu_decompose();
261  }
262  y=this->l_solve(b);
263  x=this->r_solve(y);
264  return x;
265 }
266 
267 
268 
269 
270 // spline implementation
271 // -----------------------
272 
273 void spline::set_boundary(spline::bd_type left, double left_value,
274  spline::bd_type right, double right_value,
275  bool force_linear_extrapolation)
276 {
277  assert(m_x.size()==0); // set_points() must not have happened yet
278  m_left=left;
279  m_right=right;
280  m_left_value=left_value;
281  m_right_value=right_value;
282  m_force_linear_extrapolation=force_linear_extrapolation;
283 }
284 
285 
286 void spline::set_points(const std::vector<double>& x,
287  const std::vector<double>& y, bool cubic_spline)
288 {
289  assert(x.size()==y.size());
290  assert(x.size()>2);
291  m_x=x;
292  m_y=y;
293  int n=x.size();
294  // TODO: maybe sort x and y, rather than returning an error
295  for(int i=0; i<n-1; i++) {
296  assert(m_x[i]<m_x[i+1]);
297  }
298 
299  if(cubic_spline==true) { // cubic spline interpolation
300  // setting up the matrix and right hand side of the equation system
301  // for the parameters b[]
302  band_matrix A(n,1,1);
303  std::vector<double> rhs(n);
304  for(int i=1; i<n-1; i++) {
305  A(i,i-1)=1.0/3.0*(x[i]-x[i-1]);
306  A(i,i)=2.0/3.0*(x[i+1]-x[i-1]);
307  A(i,i+1)=1.0/3.0*(x[i+1]-x[i]);
308  rhs[i]=(y[i+1]-y[i])/(x[i+1]-x[i]) - (y[i]-y[i-1])/(x[i]-x[i-1]);
309  }
310  // boundary conditions
311  if(m_left == spline::second_deriv) {
312  // 2*b[0] = f''
313  A(0,0)=2.0;
314  A(0,1)=0.0;
315  rhs[0]=m_left_value;
316  } else if(m_left == spline::first_deriv) {
317  // c[0] = f', needs to be re-expressed in terms of b:
318  // (2b[0]+b[1])(x[1]-x[0]) = 3 ((y[1]-y[0])/(x[1]-x[0]) - f')
319  A(0,0)=2.0*(x[1]-x[0]);
320  A(0,1)=1.0*(x[1]-x[0]);
321  rhs[0]=3.0*((y[1]-y[0])/(x[1]-x[0])-m_left_value);
322  } else {
323  assert(false);
324  }
325  if(m_right == spline::second_deriv) {
326  // 2*b[n-1] = f''
327  A(n-1,n-1)=2.0;
328  A(n-1,n-2)=0.0;
329  rhs[n-1]=m_right_value;
330  } else if(m_right == spline::first_deriv) {
331  // c[n-1] = f', needs to be re-expressed in terms of b:
332  // (b[n-2]+2b[n-1])(x[n-1]-x[n-2])
333  // = 3 (f' - (y[n-1]-y[n-2])/(x[n-1]-x[n-2]))
334  A(n-1,n-1)=2.0*(x[n-1]-x[n-2]);
335  A(n-1,n-2)=1.0*(x[n-1]-x[n-2]);
336  rhs[n-1]=3.0*(m_right_value-(y[n-1]-y[n-2])/(x[n-1]-x[n-2]));
337  } else {
338  assert(false);
339  }
340 
341  // solve the equation system to obtain the parameters b[]
342  m_b=A.lu_solve(rhs);
343 
344  // calculate parameters a[] and c[] based on b[]
345  m_a.resize(n);
346  m_c.resize(n);
347  for(int i=0; i<n-1; i++) {
348  m_a[i]=1.0/3.0*(m_b[i+1]-m_b[i])/(x[i+1]-x[i]);
349  m_c[i]=(y[i+1]-y[i])/(x[i+1]-x[i])
350  - 1.0/3.0*(2.0*m_b[i]+m_b[i+1])*(x[i+1]-x[i]);
351  }
352  } else { // linear interpolation
353  m_a.resize(n);
354  m_b.resize(n);
355  m_c.resize(n);
356  for(int i=0; i<n-1; i++) {
357  m_a[i]=0.0;
358  m_b[i]=0.0;
359  m_c[i]=(m_y[i+1]-m_y[i])/(m_x[i+1]-m_x[i]);
360  }
361  }
362 
363  // for left extrapolation coefficients
364  m_b0 = (m_force_linear_extrapolation==false) ? m_b[0] : 0.0;
365  m_c0 = m_c[0];
366 
367  // for the right extrapolation coefficients
368  // f_{n-1}(x) = b*(x-x_{n-1})^2 + c*(x-x_{n-1}) + y_{n-1}
369  double h=x[n-1]-x[n-2];
370  // m_b[n-1] is determined by the boundary condition
371  m_a[n-1]=0.0;
372  m_c[n-1]=3.0*m_a[n-2]*h*h+2.0*m_b[n-2]*h+m_c[n-2]; // = f'_{n-2}(x_{n-1})
373  if(m_force_linear_extrapolation==true)
374  m_b[n-1]=0.0;
375 }
376 
377 double spline::operator() (double x) const
378 {
379  size_t n=m_x.size();
380  // find the closest point m_x[idx] < x, idx=0 even if x<m_x[0]
381  std::vector<double>::const_iterator it;
382  it=std::lower_bound(m_x.begin(),m_x.end(),x);
383  int idx=std::max( int(it-m_x.begin())-1, 0);
384 
385  double h=x-m_x[idx];
386  double interpol;
387  if(x<m_x[0]) {
388  // extrapolation to the left
389  interpol=(m_b0*h + m_c0)*h + m_y[0];
390  } else if(x>m_x[n-1]) {
391  // extrapolation to the right
392  interpol=(m_b[n-1]*h + m_c[n-1])*h + m_y[n-1];
393  } else {
394  // interpolation
395  interpol=((m_a[idx]*h + m_b[idx])*h + m_c[idx])*h + m_y[idx];
396  }
397  return interpol;
398 }
399 
400 
401 } // namespace tk
402 
403 
404 } // namespace
405 
406 #endif /* TK_SPLINE_H */
Definition: spline.h:43