spline1.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. // Copyright 2012 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview A one dimensional cubic spline interpolator with not-a-knot
  16. * boundary conditions.
  17. *
  18. * See http://en.wikipedia.org/wiki/Spline_interpolation.
  19. *
  20. */
  21. goog.provide('goog.math.interpolator.Spline1');
  22. goog.require('goog.array');
  23. goog.require('goog.asserts');
  24. goog.require('goog.math');
  25. goog.require('goog.math.interpolator.Interpolator1');
  26. goog.require('goog.math.tdma');
  27. /**
  28. * A one dimensional cubic spline interpolator with natural boundary conditions.
  29. * @implements {goog.math.interpolator.Interpolator1}
  30. * @constructor
  31. */
  32. goog.math.interpolator.Spline1 = function() {
  33. /**
  34. * The abscissa of the data points.
  35. * @type {!Array<number>}
  36. * @private
  37. */
  38. this.x_ = [];
  39. /**
  40. * The spline interval coefficients.
  41. * Note that, in general, the length of coeffs and x is not the same.
  42. * @type {!Array<!Array<number>>}
  43. * @private
  44. */
  45. this.coeffs_ = [[0, 0, 0, Number.NaN]];
  46. };
  47. /** @override */
  48. goog.math.interpolator.Spline1.prototype.setData = function(x, y) {
  49. goog.asserts.assert(
  50. x.length == y.length,
  51. 'input arrays to setData should have the same length');
  52. if (x.length > 0) {
  53. this.coeffs_ = this.computeSplineCoeffs_(x, y);
  54. this.x_ = x.slice();
  55. } else {
  56. this.coeffs_ = [[0, 0, 0, Number.NaN]];
  57. this.x_ = [];
  58. }
  59. };
  60. /** @override */
  61. goog.math.interpolator.Spline1.prototype.interpolate = function(x) {
  62. var pos = goog.array.binarySearch(this.x_, x);
  63. if (pos < 0) {
  64. pos = -pos - 2;
  65. }
  66. pos = goog.math.clamp(pos, 0, this.coeffs_.length - 1);
  67. var d = x - this.x_[pos];
  68. var d2 = d * d;
  69. var d3 = d2 * d;
  70. var coeffs = this.coeffs_[pos];
  71. return coeffs[0] * d3 + coeffs[1] * d2 + coeffs[2] * d + coeffs[3];
  72. };
  73. /**
  74. * Solve for the spline coefficients such that the spline precisely interpolates
  75. * the data points.
  76. * @param {Array<number>} x The abscissa of the spline data points.
  77. * @param {Array<number>} y The ordinate of the spline data points.
  78. * @return {!Array<!Array<number>>} The spline interval coefficients.
  79. * @private
  80. */
  81. goog.math.interpolator.Spline1.prototype.computeSplineCoeffs_ = function(x, y) {
  82. var nIntervals = x.length - 1;
  83. var dx = new Array(nIntervals);
  84. var delta = new Array(nIntervals);
  85. for (var i = 0; i < nIntervals; ++i) {
  86. dx[i] = x[i + 1] - x[i];
  87. delta[i] = (y[i + 1] - y[i]) / dx[i];
  88. }
  89. // Compute the spline coefficients from the 1st order derivatives.
  90. var coeffs = [];
  91. if (nIntervals == 0) {
  92. // Nearest neighbor interpolation.
  93. coeffs[0] = [0, 0, 0, y[0]];
  94. } else if (nIntervals == 1) {
  95. // Straight line interpolation.
  96. coeffs[0] = [0, 0, delta[0], y[0]];
  97. } else if (nIntervals == 2) {
  98. // Parabola interpolation.
  99. var c3 = 0;
  100. var c2 = (delta[1] - delta[0]) / (dx[0] + dx[1]);
  101. var c1 = delta[0] - c2 * dx[0];
  102. var c0 = y[0];
  103. coeffs[0] = [c3, c2, c1, c0];
  104. } else {
  105. // General Spline interpolation. Compute the 1st order derivatives from
  106. // the Spline equations.
  107. var deriv = this.computeDerivatives(dx, delta);
  108. for (var i = 0; i < nIntervals; ++i) {
  109. var c3 = (deriv[i] - 2 * delta[i] + deriv[i + 1]) / (dx[i] * dx[i]);
  110. var c2 = (3 * delta[i] - 2 * deriv[i] - deriv[i + 1]) / dx[i];
  111. var c1 = deriv[i];
  112. var c0 = y[i];
  113. coeffs[i] = [c3, c2, c1, c0];
  114. }
  115. }
  116. return coeffs;
  117. };
  118. /**
  119. * Computes the derivative at each point of the spline such that
  120. * the curve is C2. It uses not-a-knot boundary conditions.
  121. * @param {Array<number>} dx The spacing between consecutive data points.
  122. * @param {Array<number>} slope The slopes between consecutive data points.
  123. * @return {!Array<number>} The Spline derivative at each data point.
  124. * @protected
  125. */
  126. goog.math.interpolator.Spline1.prototype.computeDerivatives = function(
  127. dx, slope) {
  128. var nIntervals = dx.length;
  129. // Compute the main diagonal of the system of equations.
  130. var mainDiag = new Array(nIntervals + 1);
  131. mainDiag[0] = dx[1];
  132. for (var i = 1; i < nIntervals; ++i) {
  133. mainDiag[i] = 2 * (dx[i] + dx[i - 1]);
  134. }
  135. mainDiag[nIntervals] = dx[nIntervals - 2];
  136. // Compute the sub diagonal of the system of equations.
  137. var subDiag = new Array(nIntervals);
  138. for (var i = 0; i < nIntervals; ++i) {
  139. subDiag[i] = dx[i + 1];
  140. }
  141. subDiag[nIntervals - 1] = dx[nIntervals - 2] + dx[nIntervals - 1];
  142. // Compute the super diagonal of the system of equations.
  143. var supDiag = new Array(nIntervals);
  144. supDiag[0] = dx[0] + dx[1];
  145. for (var i = 1; i < nIntervals; ++i) {
  146. supDiag[i] = dx[i - 1];
  147. }
  148. // Compute the right vector of the system of equations.
  149. var vecRight = new Array(nIntervals + 1);
  150. vecRight[0] =
  151. ((dx[0] + 2 * supDiag[0]) * dx[1] * slope[0] + dx[0] * dx[0] * slope[1]) /
  152. supDiag[0];
  153. for (var i = 1; i < nIntervals; ++i) {
  154. vecRight[i] = 3 * (dx[i] * slope[i - 1] + dx[i - 1] * slope[i]);
  155. }
  156. vecRight[nIntervals] =
  157. (dx[nIntervals - 1] * dx[nIntervals - 1] * slope[nIntervals - 2] +
  158. (2 * subDiag[nIntervals - 1] + dx[nIntervals - 1]) * dx[nIntervals - 2] *
  159. slope[nIntervals - 1]) /
  160. subDiag[nIntervals - 1];
  161. // Solve the system of equations.
  162. var deriv = goog.math.tdma.solve(subDiag, mainDiag, supDiag, vecRight);
  163. return deriv;
  164. };
  165. /**
  166. * Note that the inverse of a cubic spline is not a cubic spline in general.
  167. * As a result the inverse implementation is only approximate. In
  168. * particular, it only guarantees the exact inverse at the original input data
  169. * points passed to setData.
  170. * @override
  171. */
  172. goog.math.interpolator.Spline1.prototype.getInverse = function() {
  173. var interpolator = new goog.math.interpolator.Spline1();
  174. var y = [];
  175. for (var i = 0; i < this.x_.length; i++) {
  176. y[i] = this.interpolate(this.x_[i]);
  177. }
  178. interpolator.setData(y, this.x_);
  179. return interpolator;
  180. };