00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025 #ifdef BTSPLINE_MATHEMATICS
00026
00027 A 1-dimensional cubic spline is defined as a function which is in each of
00028 some set of intervals is a cubic polynomials in the input coordinate which
00029 matches the values and derivatives of some function being approximated.
00030 These values and derivatives are in tables which remain const and characterise
00031 the spline.
00032
00033 The first step is to find where in the table you are: Once you localize to an
00034 interval, you work with the fractional distance dx from the lower end of the
00035 interval to the actual point being evaluated.
00036
00037 The spline in 1 dimension consists of adding 4 terms based on x and the value
00038 at each end or the derivative at each end. If instead of gradients only values
00039 are supplied, instead the spline uses second derivatives at the endpoints.
00040 #endif
00041
00042 #include "BTSpline1D.hh"
00043 #include <iostream>
00044 #include <cstdlib>
00045
00046 BTSpline1D::BTSpline1D (const BTSpline1D &s): grads_(0), secondDerivs_(0) {
00047 extent_ = s.extent_;
00048 int i;
00049 nodePoints_ = new Data_t[extent_];
00050 distances_ = new Data_t[extent_];
00051 values_ = new Data_t[extent_];
00052 for (i=0; i<extent_; ++i) {
00053 nodePoints_[i] = s.nodePoints_[i];
00054 }
00055 for (i=0; i<extent_; ++i) {
00056 distances_[i] = s.distances_[i];
00057 }
00058 for (i=0; i<extent_; ++i) {
00059 values_[i] = s.values_[i];
00060 }
00061 if (s.grads_) {
00062 grads_ = new Data_t[extent_];
00063 for (i=0; i<extent_; ++i) {
00064 grads_[i] = s.grads_[i];
00065 }
00066 }
00067 if (s.secondDerivs_) {
00068 secondDerivs_ = new Data_t[extent_];
00069 for (i=0; i<extent_; ++i) {
00070 secondDerivs_[i] = s.secondDerivs_[i];
00071 }
00072 }
00073
00074 }
00075
00076
00077 BTSpline1D::BTSpline1D(): extent_(0), nodePoints_(0),
00078 values_(0), grads_(0), secondDerivs_(0){}
00079
00080 void BTSpline1D::captureGrid ( int extent, const Data_t* nodes ) {
00081
00082
00083
00084
00085 int i;
00086
00087
00088
00089 extent_ = extent;
00090
00091 nodePoints_ = new Data_t[extent];
00092 for ( i = 0; i < extent_; i++ ) {
00093 nodePoints_[i] = nodes[i];
00094 }
00095
00096
00097
00098 distances_ = new Data_t[extent];
00099 for ( i = 0; i < extent_-1; i++ ) {
00100 distances_[i] = nodePoints_[i+1] - nodePoints_[i];
00101 if ( distances_[i] <= 0 ) {
00102 std::cout <<
00103 "Spline1D initialized with nodePoints not monotonic increasing\n";
00104 std::abort();
00105 }
00106 }
00107
00108 }
00109
00110
00111 void BTSpline1D::captureValues ( int extent, const Data_t* values ) {
00112 values_ = new Data_t[extent_];
00113 const Data_t* valp = values;
00114 Data_t* vp = values_;
00115 for ( int i=0; i < extent_; i++ ) {
00116 *vp++ = *valp++;
00117 }
00118 }
00119
00120
00121 void BTSpline1D::fillValues ( int extent, double values_function(double x)) {
00122 values_ = new Data_t[extent_];
00123 double x;
00124 Data_t* vp = values_;
00125 for ( int i = 0; i < extent_; i++ ) {
00126 x = nodePoints_[i];
00127 *vp = values_function ( x );
00128 vp++;
00129 }
00130 }
00131
00132
00133 void BTSpline1D::computeSecondDerivs () {
00134
00135 secondDerivs_ = new Data_t[extent_];
00136
00137
00138
00139 #ifdef MATH
00140
00141 For 1 <= j <= N-2, we have equations matching first derivatives at the
00142 node points. These are
00143
00144 distance[j-1]/6 * y[j-1]
00145 + (distance[j]+distance[j-1])/3 * y[j]
00146 + distance[j]/6 * y[j+1]
00147 = dyRight/dxRight - dyLeft/dxLeft
00148
00149 We also have, for "natural" boundary conditions, y2[0] = y2[N-1] = 0.
00150 (Or for specified slopes, a different relation, reflected in the elses.)
00151
00152 So our equations are
00153
00154 M Y2 = S
00155
00156 where Y2 are the desired second derivatives,
00157
00158 S is a vector {0, (dyRight/dxRight - dyLeft/dxLeft) from 1...N-2, 0}
00159 or for slope boundaries,
00160 S[0] = 3 * ( (y[1]-y[0])/(x[1]-x[0]) - v ) / (x[1]-x[0]) = S(v)
00161 S[N-1] = 3 * ( w - (y[N-1]-y[N-2])/(x[N-1]-x[N-2]) ) / (x[N-1]-x[N-2]) = S(w)
00162
00163 and M is a matrix of the form
00164
00165 1 0 or .5
00166 d0/6 (d0+d1)/3 d1/6
00167 d1/6 (d1+d2)/3 d2/6
00168 d2/6 (d2+d3)/3 d3/6
00169 ...
00170 dN-3/6 (dN-3+DN-2)/3 DN-2/3
00171 0 or .5 1
00172
00173 Step 1 in solving this tridiagonal system is to eliminate the lower part.
00174 That only affects the first rank above the diagonal; we will call the
00175 affected rank u. (u is a vector with indices ranging from 0 to N-2 (not N-1)).
00176 By inspection of M, u[0] starts out as 0 for our natural condition.
00177
00178 We will also hold S in the place where we want y2 to end up. S[0] starts out
00179 as 0 as well, for the natural condition.
00180
00181 We will not form u in advance, nor form the below-diagonal, because they are
00182 simple expressions in terns of the distances. For each row, we get a new
00183 pivot from the original diagonal element modified by the u above it when
00184 eliminating the below-diagonal term, and a new y2 similarly, and then we divide
00185 thru by the pivot.
00186 #endif
00187
00188
00189
00190
00191 double* u = new double[extent_];
00192
00193 Data_t* y = values_;
00194 Data_t* y2 = secondDerivs_;
00195 int N = extent_;
00196
00197 if (naturalBoundaryConditions) {
00198 y2[0] = u[0] = 0;
00199 }
00200 else {
00201 y2[0] = 3 * ( (y[1]-y[0])/distances_[0] - slope0_ ) / distances_[0];
00202 }
00203
00204 double dxLeft;
00205 double dyLeft;
00206 double dxRight;
00207 double dyRight;
00208 double pivot;
00209 for ( int i=1; i < N-1; ++i ) {
00210 dxLeft = distances_[i-1];
00211 dxRight = distances_[i];
00212 dyLeft = y[i] - y[i-1];
00213 dyRight = y[i+1]- y[i];
00214 pivot = (dxLeft+dxRight)/3.0 - dxLeft * u[i-1] / 6.0;
00215 y2[i] = dyRight/dxRight - dyLeft/dxLeft - dxLeft * y2[i-1] / 6.0;
00216 u[i] = dxRight / (6.0 * pivot);
00217 y2[i] = y2[i] / pivot;
00218 }
00219
00220 double lower;
00221 if (naturalBoundaryConditions) {
00222 y2[N-1] = 0;
00223 lower = 0;
00224 }
00225 else {
00226 y2[N-1] =
00227 3 * ( slopeN_ - (y[N-1]-y[N-2])/distances_[N-2] ) / distances_[N-2];
00228 lower = .5;
00229 }
00230 pivot = 1 - lower*u[N-2];
00231 y2[N-1] -= lower * y2[N-2];
00232 y2[N-1] = y2[N-1] / pivot;
00233
00234
00235
00236 for ( int k=N-2; k >= 0; --k) {
00237 y2[k] -= u[k] * y2[k+1];
00238 }
00239
00240
00241
00242 delete[] u;
00243
00244 return;
00245 }
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255
00256 BTSpline1D::BTSpline1D (
00257 int extent,
00258 const Data_t* nodes,
00259 const Data_t* values
00260 ) : grads_(0), naturalBoundaryConditions (true) {
00261
00262 captureGrid ( extent, nodes );
00263 captureValues ( extent, values );
00264 computeSecondDerivs ();
00265
00266 }
00267
00268
00269 BTSpline1D::BTSpline1D (
00270 int extent,
00271 const Data_t* nodes,
00272 double values_function (double x)
00273 ) : grads_(0), naturalBoundaryConditions (true) {
00274
00275 captureGrid ( extent, nodes );
00276 fillValues (extent, values_function);
00277 computeSecondDerivs ();
00278
00279 }
00280
00281
00282 BTSpline1D::BTSpline1D (
00283 int extent,
00284 const Data_t* nodes,
00285 const Data_t* values,
00286 Data_t slope0, Data_t slopeN
00287 ) : grads_(0), naturalBoundaryConditions (false),
00288 slope0_ (slope0), slopeN_ (slopeN) {
00289
00290 captureGrid ( extent, nodes );
00291 captureValues ( extent, values );
00292 computeSecondDerivs ();
00293
00294 }
00295
00296
00297 BTSpline1D::BTSpline1D (
00298 int extent,
00299 const Data_t* nodes,
00300 double values_function (double x),
00301 Data_t slope0, Data_t slopeN
00302 ) : grads_(0), naturalBoundaryConditions (false),
00303 slope0_ (slope0), slopeN_ (slopeN) {
00304
00305 captureGrid ( extent, nodes );
00306 fillValues (extent, values_function);
00307 computeSecondDerivs ();
00308
00309 }
00310
00311
00312
00313
00314
00315
00316
00317
00318 BTSpline1D::BTSpline1D (
00319 int extent,
00320 const Data_t* nodes,
00321 const Data_t* values,
00322 const Data_t* gradients
00323
00324
00325 ) : secondDerivs_(0) {
00326
00327 captureGrid ( extent, nodes );
00328
00329
00330
00331
00332
00333 const Data_t* valp = values;
00334 const Data_t* gradp = gradients;
00335 Data_t* vp = values_;
00336 Data_t* gp = grads_;
00337 for ( int i=0; i < extent_; i++ ) {
00338 *vp++ = *valp++;
00339 *gp++ = *gradp++;
00340 }
00341
00342 }
00343
00344
00345 BTSpline1D::BTSpline1D (
00346 int extent,
00347 const Data_t* nodes,
00348 void values_grads_function (
00349 double x,
00350 double* val,
00351 double* grads)
00352
00353 ) : secondDerivs_(0) {
00354
00355 captureGrid ( extent, nodes );
00356
00357
00358
00359
00360 float x;
00361 Data_t* vp = values_;
00362 Data_t* gp = grads_;
00363 double vpTemp;
00364 double gpTemp;
00365 for ( int i = 0; i < extent_; i++ ) {
00366 x = nodePoints_[i];
00367 values_grads_function ( x, &vpTemp, &gpTemp );
00368 *vp++ = vpTemp;
00369 *gp++ = gpTemp;
00370 }
00371
00372 }
00373
00374
00375 BTSpline1D::~BTSpline1D () {
00376
00377 delete[] nodePoints_;
00378 delete[] distances_;
00379 delete[] values_;
00380 if (grads_) delete[] grads_;
00381 if (secondDerivs_) delete[] secondDerivs_;
00382
00383 }
00384
00385
00386 double BTSpline1D::operator() ( double x ) const {
00387
00388
00389
00390
00391
00392
00393
00394 double f0;
00395 double f1;
00396 double g0;
00397 double g1;
00398
00399 int a = 0;
00400
00401
00402 int b = extent_ - 1;
00403
00404
00405 while (b != (a+1) ) {
00406 int c = (a+b+1)>>1;
00407 if (x > nodePoints_[c]) {
00408 a = c;
00409 }
00410 else {
00411 b = c;
00412 }
00413 }
00414
00415
00416
00417
00418 double spline;
00419
00420 double dx;
00421 double oneMinusDx;
00422
00423 dx = (x - nodePoints_[a])/distances_[a];
00424 oneMinusDx = 1 - dx;
00425
00426 if ( grads_ ) {
00427
00428 double oneMinusX2;
00429 double x2;
00430
00431 x2 = dx * dx;
00432 oneMinusX2 = oneMinusDx * oneMinusDx;
00433
00434 f0 = (2. * dx + 1.) * oneMinusX2;
00435 f1 = (3. - 2. * dx) * x2;
00436 g0 = distances_[a] * dx * oneMinusX2;
00437 g1 = -distances_[a] * oneMinusDx * x2;
00438
00439
00440
00441
00442 spline = f0 * values_[a] + f1 * values_[a+1]
00443 + g0 * grads_[a] + g1 * grads_[a+1] ;
00444
00445 }
00446 else {
00447 double spacingFactor = distances_[a] * distances_[a] / 6.0;
00448 double leftCubic = (dx*dx*dx - dx) * secondDerivs_[b];
00449 double rightCubic =
00450 (oneMinusDx*oneMinusDx*oneMinusDx-oneMinusDx) * secondDerivs_[a];
00451 spline = dx * values_[b] + oneMinusDx * values_[a]
00452 + (leftCubic + rightCubic) * spacingFactor;
00453 }
00454
00455 return spline;
00456
00457 }
00458
00459
00460 #ifdef BTSPLINE_MATHEMATICS
00461
00462 The spline in 1 dimensions consists of adding 4 terms, each of
00463 which is a cubic polynomials in dx times a value or gradient component
00464 of the function phi at one of ends of the interval.
00465
00466 These polynomials are, for the non-gradient terms,
00467
00468 f0(x) = (2*x +1) * (1-x)**2
00469 f1(x) = (3 - 2*x) * x**2
00470
00471 f0 refers to the lower end and f1 to the upper end; notice that these
00472 are linear with some correction which near 1 varies slowly for f1, and near
00473 0 varies slowly for f0.
00474
00475 For the gradient terms, the polynomials contain a factor of the size h of
00476 the interval:
00477
00478 g0(x) = h * x * (1-x)**2
00479 g1(x) = - h * (1-x) * x**2
00480
00481 These are found in about 15 operations a given value of dx.
00482
00483 Adding these four gives an interpolation which matches both the function and
00484 its first derivation at both ends of the interval.
00485 #endif