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