Path: blob/trunk/third_party/closure/goog/math/interpolator/spline1.js
2868 views
// Copyright 2012 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview A one dimensional cubic spline interpolator with not-a-knot16* boundary conditions.17*18* See http://en.wikipedia.org/wiki/Spline_interpolation.19*20*/2122goog.provide('goog.math.interpolator.Spline1');2324goog.require('goog.array');25goog.require('goog.asserts');26goog.require('goog.math');27goog.require('goog.math.interpolator.Interpolator1');28goog.require('goog.math.tdma');29303132/**33* A one dimensional cubic spline interpolator with natural boundary conditions.34* @implements {goog.math.interpolator.Interpolator1}35* @constructor36*/37goog.math.interpolator.Spline1 = function() {38/**39* The abscissa of the data points.40* @type {!Array<number>}41* @private42*/43this.x_ = [];4445/**46* The spline interval coefficients.47* Note that, in general, the length of coeffs and x is not the same.48* @type {!Array<!Array<number>>}49* @private50*/51this.coeffs_ = [[0, 0, 0, Number.NaN]];52};535455/** @override */56goog.math.interpolator.Spline1.prototype.setData = function(x, y) {57goog.asserts.assert(58x.length == y.length,59'input arrays to setData should have the same length');60if (x.length > 0) {61this.coeffs_ = this.computeSplineCoeffs_(x, y);62this.x_ = x.slice();63} else {64this.coeffs_ = [[0, 0, 0, Number.NaN]];65this.x_ = [];66}67};686970/** @override */71goog.math.interpolator.Spline1.prototype.interpolate = function(x) {72var pos = goog.array.binarySearch(this.x_, x);73if (pos < 0) {74pos = -pos - 2;75}76pos = goog.math.clamp(pos, 0, this.coeffs_.length - 1);7778var d = x - this.x_[pos];79var d2 = d * d;80var d3 = d2 * d;81var coeffs = this.coeffs_[pos];82return coeffs[0] * d3 + coeffs[1] * d2 + coeffs[2] * d + coeffs[3];83};848586/**87* Solve for the spline coefficients such that the spline precisely interpolates88* the data points.89* @param {Array<number>} x The abscissa of the spline data points.90* @param {Array<number>} y The ordinate of the spline data points.91* @return {!Array<!Array<number>>} The spline interval coefficients.92* @private93*/94goog.math.interpolator.Spline1.prototype.computeSplineCoeffs_ = function(x, y) {95var nIntervals = x.length - 1;96var dx = new Array(nIntervals);97var delta = new Array(nIntervals);98for (var i = 0; i < nIntervals; ++i) {99dx[i] = x[i + 1] - x[i];100delta[i] = (y[i + 1] - y[i]) / dx[i];101}102103// Compute the spline coefficients from the 1st order derivatives.104var coeffs = [];105if (nIntervals == 0) {106// Nearest neighbor interpolation.107coeffs[0] = [0, 0, 0, y[0]];108} else if (nIntervals == 1) {109// Straight line interpolation.110coeffs[0] = [0, 0, delta[0], y[0]];111} else if (nIntervals == 2) {112// Parabola interpolation.113var c3 = 0;114var c2 = (delta[1] - delta[0]) / (dx[0] + dx[1]);115var c1 = delta[0] - c2 * dx[0];116var c0 = y[0];117coeffs[0] = [c3, c2, c1, c0];118} else {119// General Spline interpolation. Compute the 1st order derivatives from120// the Spline equations.121var deriv = this.computeDerivatives(dx, delta);122for (var i = 0; i < nIntervals; ++i) {123var c3 = (deriv[i] - 2 * delta[i] + deriv[i + 1]) / (dx[i] * dx[i]);124var c2 = (3 * delta[i] - 2 * deriv[i] - deriv[i + 1]) / dx[i];125var c1 = deriv[i];126var c0 = y[i];127coeffs[i] = [c3, c2, c1, c0];128}129}130return coeffs;131};132133134/**135* Computes the derivative at each point of the spline such that136* the curve is C2. It uses not-a-knot boundary conditions.137* @param {Array<number>} dx The spacing between consecutive data points.138* @param {Array<number>} slope The slopes between consecutive data points.139* @return {!Array<number>} The Spline derivative at each data point.140* @protected141*/142goog.math.interpolator.Spline1.prototype.computeDerivatives = function(143dx, slope) {144var nIntervals = dx.length;145146// Compute the main diagonal of the system of equations.147var mainDiag = new Array(nIntervals + 1);148mainDiag[0] = dx[1];149for (var i = 1; i < nIntervals; ++i) {150mainDiag[i] = 2 * (dx[i] + dx[i - 1]);151}152mainDiag[nIntervals] = dx[nIntervals - 2];153154// Compute the sub diagonal of the system of equations.155var subDiag = new Array(nIntervals);156for (var i = 0; i < nIntervals; ++i) {157subDiag[i] = dx[i + 1];158}159subDiag[nIntervals - 1] = dx[nIntervals - 2] + dx[nIntervals - 1];160161// Compute the super diagonal of the system of equations.162var supDiag = new Array(nIntervals);163supDiag[0] = dx[0] + dx[1];164for (var i = 1; i < nIntervals; ++i) {165supDiag[i] = dx[i - 1];166}167168// Compute the right vector of the system of equations.169var vecRight = new Array(nIntervals + 1);170vecRight[0] =171((dx[0] + 2 * supDiag[0]) * dx[1] * slope[0] + dx[0] * dx[0] * slope[1]) /172supDiag[0];173for (var i = 1; i < nIntervals; ++i) {174vecRight[i] = 3 * (dx[i] * slope[i - 1] + dx[i - 1] * slope[i]);175}176vecRight[nIntervals] =177(dx[nIntervals - 1] * dx[nIntervals - 1] * slope[nIntervals - 2] +178(2 * subDiag[nIntervals - 1] + dx[nIntervals - 1]) * dx[nIntervals - 2] *179slope[nIntervals - 1]) /180subDiag[nIntervals - 1];181182// Solve the system of equations.183var deriv = goog.math.tdma.solve(subDiag, mainDiag, supDiag, vecRight);184185return deriv;186};187188189/**190* Note that the inverse of a cubic spline is not a cubic spline in general.191* As a result the inverse implementation is only approximate. In192* particular, it only guarantees the exact inverse at the original input data193* points passed to setData.194* @override195*/196goog.math.interpolator.Spline1.prototype.getInverse = function() {197var interpolator = new goog.math.interpolator.Spline1();198var y = [];199for (var i = 0; i < this.x_.length; i++) {200y[i] = this.interpolate(this.x_[i]);201}202interpolator.setData(y, this.x_);203return interpolator;204};205206207