Hermes2D  3.0
spline.cpp
1 // This file is part of Hermes2D.
2 //
3 // Hermes2D is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 2 of the License, or
6 // (at your option) any later version.
7 //
8 // Hermes2D is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Hermes2D. If not, see <http://www.gnu.org/licenses/>.
15 
16 #include "spline.h"
17 #include "algebra/dense_matrix_operations.h"
18 
20 
21 namespace Hermes
22 {
23  namespace Hermes2D
24  {
25  CubicSpline::CubicSpline(std::vector<double> points, std::vector<double> values,
26  double bc_left, double bc_right,
27  bool first_der_left, bool first_der_right,
28  bool extrapolate_der_left, bool extrapolate_der_right) : Hermes::Hermes1DFunction<double>(), points(points), values(values),
29  bc_left(bc_left), bc_right(bc_right), first_der_left(first_der_left),
30  first_der_right(first_der_right), extrapolate_der_left(extrapolate_der_left),
31  extrapolate_der_right(extrapolate_der_right)
32  {
33  for (unsigned short i = 1; i < points.size(); i++)
34  if (points[i] <= points[i - 1])
35  throw Exceptions::Exception("Points must be in ascending order when constructing a spline.");
36  this->is_const = false;
37  }
38 
39  CubicSpline::CubicSpline(double const_value) : Hermes::Hermes1DFunction<double>(const_value)
40  {
41  }
42 
44  {
45  free();
46  }
47 
48  void CubicSpline::free()
49  {
50  coeffs.clear();
51  points.clear();
52  values.clear();
53  }
54 
55  double CubicSpline::value(double x) const
56  {
57  // For simple constant case.
58  if (this->is_const)
59  return const_value;
60  // For general case.
61  int m = -1;
62  if (!this->find_interval(x, m))
63  {
64  // Point lies on the left of interval of definition.
65  if (x <= point_left)
66  {
67  // Spline should be extrapolated by constant function
68  // matching the value at the end.
69  if (extrapolate_der_left == false)
70  return value_left;
71  // Spline should be extrapolated as a linear function
72  // matching the derivative at the end.
73  else return extrapolate_value(point_left, value_left, derivative_left, x);
74  }
75  // Point lies on the right of interval of definition.
76  else
77  {
78  // Spline should be extrapolated by constant function
79  // matching the value at the end.
80  if (extrapolate_der_right == false)
81  return value_right;
82  // Spline should be extrapolated as a linear function
83  // matching the derivative at the end.
84  else return extrapolate_value(point_right, value_right, derivative_right, x);
85  }
86  }
87 
88  return get_value_from_interval(x, m);
89  };
90 
91  double CubicSpline::derivative(double x) const
92  {
93  // For simple constant case.
94  if (this->is_const)
95  return 0.0;
96 
97  // For general case.
98  int m = -1;
99  if (!this->find_interval(x, m))
100  {
101  // Point lies on the left of interval of definition.
102  if (x <= point_left)
103  {
104  // Spline should be extrapolated by constant function
105  // matching the value at the end.
106  if (extrapolate_der_left == false) return 0;
107  // Spline should be extrapolated as a linear function
108  // matching the derivative at the end.
109  else return derivative_left;
110  }
111  // Point lies on the right of interval of definition.
112  else
113  {
114  // Spline should be extrapolated by constant function
115  // matching the value at the end.
116  if (extrapolate_der_right == false) return 0;
117  // Spline should be extrapolated as a linear function
118  // matching the derivative at the end.
119  else return derivative_right;
120  }
121  }
122 
123  return get_derivative_from_interval(x, m);
124  };
125 
126  double CubicSpline::extrapolate_value(double point_end, double value_end,
127  double derivative_end, double x_in) const
128  {
129  return value_end + derivative_end * (x_in - point_end);
130  }
131 
132  double CubicSpline::get_value_from_interval(double x_in, int m) const
133  {
134  double x2 = x_in * x_in;
135  double x3 = x2 * x_in;
136  return this->coeffs[m].a + this->coeffs[m].b * x_in + this->coeffs[m].c * x2
137  + this->coeffs[m].d * x3;
138  }
139 
140  double CubicSpline::get_derivative_from_interval(double x_in, int m) const
141  {
142  double x2 = x_in * x_in;
143  return this->coeffs[m].b + 2 * this->coeffs[m].c * x_in
144  + 3 * this->coeffs[m].d * x2;
145  }
146 
147  bool CubicSpline::find_interval(double x_in, int &m) const
148  {
149  int i_left = 0;
150  int i_right = points.size() - 1;
151  if (i_right < 0)
152  return false;
153 
154  if (x_in < points[i_left]) return false;
155  if (x_in > points[i_right]) return false;
156 
157  while (i_left + 1 < i_right)
158  {
159  int i_mid = (i_left + i_right) / 2;
160  if (points[i_mid] < x_in) i_left = i_mid;
161  else i_right = i_mid;
162  }
163 
164  m = i_left;
165  return true;
166  };
167 
168  void CubicSpline::plot(const char* filename, double extension, bool plot_derivative, int subdiv) const
169  {
170  FILE *f = fopen(filename, "wb");
171  if (f == nullptr)
172  throw Hermes::Exceptions::Exception("Could not open a spline file for writing.");
173 
174  if (coeffs.size() == 0)
175  {
176  fclose(f);
177  throw Hermes::Exceptions::Exception("The cubic spline has no coefficients. Calculate using calculate_coeffs.");
178  }
179 
180  // Plotting on the left of the area of definition.
181  double x_left = point_left - extension;
182  double h = extension / subdiv;
183  for (int j = 0; j < subdiv; j++)
184  {
185  double x = x_left + j * h;
186  double val;
187  if (!plot_derivative) val = value(x);
188  else val = derivative(x);
189  fprintf(f, "%g %g\n", x, val);
190  }
191  double x_last = point_left;
192  double val_last;
193  if (!plot_derivative) val_last = value(x_last);
194  else val_last = derivative(x_last);
195  fprintf(f, "%g %g\n", x_last, val_last);
196 
197  // Plotting inside the interval of definition.
198  for (unsigned int i = 0; i < points.size() - 1; i++)
199  {
200  double h = (points[i + 1] - points[i]) / subdiv;
201  for (int j = 0; j < subdiv; j++)
202  {
203  double x = points[i] + j * h;
204  double val;
205  // Do not use get_value_from_interval() here.
206  if (!plot_derivative) val = this->value(x);
207  else val = derivative(x);
208  fprintf(f, "%g %g\n", x, val);
209  }
210  }
211  x_last = points[points.size() - 1];
212  // Do not use get_value_from_interval() here.
213  if (!plot_derivative) val_last = value(x_last);
214  else val_last = derivative(x_last);
215  fprintf(f, "%g %g\n", x_last, val_last);
216 
217  // Plotting on the right of the area of definition.
218  double x_right = point_right + extension;
219  h = extension / subdiv;
220  for (int j = 0; j < subdiv; j++)
221  {
222  double x = point_right + j * h;
223  double val;
224  if (!plot_derivative) val = value(x);
225  else val = derivative(x);
226  fprintf(f, "%g %g\n", x, val);
227  }
228  x_last = x_right;
229  if (!plot_derivative) val_last = value(x_last);
230  else val_last = derivative(x_last);
231  fprintf(f, "%g %g\n", x_last, val_last);
232 
233  fclose(f);
234  }
235 
237  {
238  int nelem = points.size() - 1;
239 
240  // Basic sanity checks.
241  if (points.empty() || values.empty())
242  {
243  this->warn("Empty points or values vector in CubicSpline, cancelling coefficients calculation.");
244  return;
245  }
246  if (points.size() < 2 || values.size() < 2)
247  {
248  this->warn("At least two points and values required in CubicSpline, cancelling coefficients calculation.");
249  return;
250  }
251  if (points.size() != values.size())
252  {
253  this->warn("Mismatched number of points and values in CubicSpline, cancelling coefficients calculation.");
254  return;
255  }
256 
257  // Check for improperly ordered or duplicated points.
258  double eps = Hermes::HermesEpsilon;
259  for (int i = 0; i < nelem; i++)
260  {
261  if (points[i + 1] < points[i] + eps)
262  {
263  this->warn("Duplicated or improperly ordered points in CubicSpline detected, cancelling coefficients calculation.");
264  return;
265  }
266  }
267 
268  /* START COMPUTATION */
269 
270  // Allocate matrix and rhs.
271  const int n = 4 * nelem;
272  double** matrix = new_matrix<double>(n, n);
273  for (int i = 0; i < n; i++)
274  {
275  for (int j = 0; j < n; j++)
276  {
277  matrix[i][j] = 0;
278  }
279  }
280  double* rhs = malloc_with_check<CubicSpline, double>(n, this);
281  for (int j = 0; j < n; j++)
282  {
283  rhs[j] = 0;
284  }
285 
286  // Fill the rhs vector.
287  for (int i = 0; i < nelem; i++)
288  {
289  rhs[2 * i] = values[i];
290  rhs[2 * i + 1] = values[i + 1];
291  }
292 
293  // Fill the matrix. Step 1 - match values at interval endpoints.
294  // This will generate the first 2*nelem rows.
295  for (int i = 0; i < nelem; i++) { // Loop over elements.
296  double xx = points[i];
297  double xx2 = xx*xx;
298  double xx3 = xx2 * xx;
299  matrix[2 * i][4 * i + 0] = 1;
300  matrix[2 * i][4 * i + 1] = xx;
301  matrix[2 * i][4 * i + 2] = xx2;
302  matrix[2 * i][4 * i + 3] = xx3;
303  xx = points[i + 1];
304  xx2 = xx*xx;
305  xx3 = xx2 * xx;
306  matrix[2 * i + 1][4 * i + 0] = 1.0;
307  matrix[2 * i + 1][4 * i + 1] = xx;
308  matrix[2 * i + 1][4 * i + 2] = xx2;
309  matrix[2 * i + 1][4 * i + 3] = xx3;
310  }
311 
312  // Step 2: match first derivatives at all interior points.
313  // This will generate additional n_elem-1 rows in the matrix.
314  int offset = 2 * nelem;
315  for (int i = 1; i < nelem; i++) { // Loop over internal points.
316  double xx = points[i];
317  double xx2 = xx*xx;
318  matrix[offset + i - 1][4 * (i - 1) + 1] = 1;
319  matrix[offset + i - 1][4 * (i - 1) + 2] = 2 * xx;
320  matrix[offset + i - 1][4 * (i - 1) + 3] = 3 * xx2;
321  matrix[offset + i - 1][4 * (i - 1) + 5] = -1;
322  matrix[offset + i - 1][4 * (i - 1) + 6] = -2 * xx;
323  matrix[offset + i - 1][4 * (i - 1) + 7] = -3 * xx2;
324  }
325 
326  // Step 3: match second derivatives at all interior points.
327  // This will generate additional n_elem-1 rows in the matrix.
328  offset = 2 * nelem + nelem - 1;
329  for (int i = 1; i < nelem; i++) { // Loop over internal points.
330  double xx = points[i];
331  matrix[offset + i - 1][4 * (i - 1) + 2] = 2;
332  matrix[offset + i - 1][4 * (i - 1) + 3] = 6 * xx;
333  matrix[offset + i - 1][4 * (i - 1) + 6] = -2;
334  matrix[offset + i - 1][4 * (i - 1) + 7] = -6 * xx;
335  }
336 
337  // Step 4: Additional two conditions are needed to define
338  // a cubic spline. This will generate the last two rows in
339  // the matrix. Setting the second derivative (curvature) at both
340  // endpoints equal to zero will result into "natural cubic spline",
341  // but you can also prescribe non-zero values, or decide to
342  // prescribe first derivative (slope).
343  // Choose just one of the following two variables to be True,
344  // and state the corresponding value for the derivative.
345  offset = 2 * nelem + 2 * (nelem - 1);
346  // Left end-point.
347  double xx = points[0];
348  if (first_der_left == false)
349  {
350  matrix[offset + 0][2] = 2;
351  matrix[offset + 0][3] = 6 * xx;
352  // Value of the second derivative.
353  rhs[n - 2] = bc_left;
354  }
355  else
356  {
357  matrix[offset + 0][1] = 1;
358  matrix[offset + 0][2] = 2 * xx;
359  matrix[offset + 0][3] = 3 * xx*xx;
360  // Value of the first derivative.
361  rhs[n - 2] = bc_left;
362  }
363  // Right end-point.
364  xx = points[nelem];
365  if (first_der_right == false)
366  {
367  matrix[offset + 1][n - 2] = 2;
368  matrix[offset + 1][n - 1] = 6 * xx;
369  // Value of the second derivative.
370  rhs[n - 1] = bc_right;
371  }
372  else
373  {
374  matrix[offset + 1][n - 3] = 1;
375  matrix[offset + 1][n - 2] = 2 * xx;
376  matrix[offset + 1][n - 1] = 3 * xx*xx;
377  // Value of the first derivative.
378  rhs[n - 1] = bc_right;
379  }
380 
381  // Solve the matrix problem.
382  double d;
383  int* perm = malloc_with_check<CubicSpline, int>(n, this);
384  ludcmp(matrix, n, perm, &d);
385  lubksb<double>(matrix, n, perm, rhs);
386  free_with_check(perm);
387 
388  // Copy the solution into the coeffs array.
389  coeffs.clear();
390  for (int i = 0; i < nelem; i++)
391  {
392  SplineCoeff coeff;
393  coeff.a = rhs[4 * i + 0];
394  coeff.b = rhs[4 * i + 1];
395  coeff.c = rhs[4 * i + 2];
396  coeff.d = rhs[4 * i + 3];
397  coeffs.push_back(coeff);
398  }
399 
400  // Define end point values and derivatives so that
401  // the points[] and values[] arrays are no longer
402  // needed.
403  point_left = points[0];
404  value_left = values[0];
405  derivative_left = get_derivative_from_interval(point_left, 0);
406  point_right = points[points.size() - 1];
407  value_right = values[values.size() - 1];
408  derivative_right = get_derivative_from_interval(point_right, points.size() - 2);
409 
410  // Free the matrix and rhs vector.
411  free_with_check(matrix);
412  free_with_check(rhs);
413 
414  return;
415  }
416  }
417 }
Definition: adapt.h:24
std::vector< double > points
Grid points, ordered.
Definition: spline.h:72
std::vector< SplineCoeff > coeffs
A set of four coefficients a, b, c, d for an elementary cubic spline.
Definition: spline.h:97
bool find_interval(double x_in, int &m) const
Definition: spline.cpp:147
double get_value_from_interval(double x_in, int m) const
Gets value at a point that lies in interval 'm'.
Definition: spline.cpp:132
void plot(const char *filename, double extension, bool plot_derivative=false, int subdiv=50) const
Definition: spline.cpp:168
~CubicSpline()
Destructor.
Definition: spline.cpp:43
double point_left
Values and derivatives at end points for extrapolation purposes.
Definition: spline.h:93
double extrapolate_value(double point_end, double value_end, double derivative_end, double x_in) const
Extrapolate the value of the spline outside of its interval of definition.
Definition: spline.cpp:126
void calculate_coeffs()
Calculates coefficients.
Definition: spline.cpp:236
CubicSpline(std::vector< double > points, std::vector< double > values, double bc_left, double bc_right, bool first_der_left=true, bool first_der_right=true, bool extend_der_left=true, bool extend_der_right=true)
Constructor (general case).
Definition: spline.cpp:25
double bc_left
Boundary conditions.
Definition: spline.h:78
double derivative(double x) const
One-dimensional function derivative value.
Definition: spline.cpp:91
double get_derivative_from_interval(double x_in, int m) const
Gets derivative at a point that lies in interval 'm'.
Definition: spline.cpp:140
std::vector< double > values
Values at the grid points.
Definition: spline.h:75
double value(double x) const
One-dimensional function value.
Definition: spline.cpp:55