UNPKG

frame.akima

Version:

A package for Akima interpolation

191 lines (190 loc) 7.61 kB
import { checkStrictlyIncreasing, evaluatePolySegment, trimPoly, } from './interpolation-utils'; const { EPSILON } = Number; /** * Returns a function that computes a cubic spline interpolation for the data * set using the Akima algorithm, as originally formulated by Hiroshi Akima in * his 1970 paper "A New Method of Interpolation and Smooth Curve Fitting Based * on Local Procedures." * J. ACM 17, 4 (October 1970), 589-602. DOI=10.1145/321607.321609 * http://doi.acm.org/10.1145/321607.321609 * * This implementation is based on the Akima implementation in the CubicSpline * class in the Math.NET Numerics library. The method referenced is * CubicSpline.InterpolateAkimaSorted. * * Returns a polynomial spline function consisting of n cubic polynomials, * defined over the subintervals determined by the x values, * x[0] < x[1] < ... < x[n-1]. * The Akima algorithm requires that n >= 5. * * @param xVals * The arguments of the interpolation points, in strictly increasing order. * @param yVals * The values of the interpolation points. * @returns * A function which interpolates the dataset. */ export function createAkimaSplineInterpolator(xVals, yVals, isPolar = true, gapInterpolation = 5) { const xValues = []; const yValues = []; if (isPolar) { for (let i = gapInterpolation - 1; i >= 0; i--) { xValues.push(xVals[xVals.length - 1 - i] - 360); yValues.push(yVals[yVals.length - 1 - i]); } for (let i = 0; i < xVals.length; i++) { xValues.push(xVals[i]); yValues.push(yVals[i]); } // Array.from(xVals).forEach((x) => { // xValues.push(x); // }); // Array.from(yVals).forEach((y) => { // yValues.push(y); // }); for (let i = 0; i < gapInterpolation; i++) { xValues.push(xVals[i] + 360); yValues.push(yVals[i]); } } else { for (let i = 0; i < xVals.length; i++) { xValues.push(xVals[i]); yValues.push(yVals[i]); } // Array.from(xVals).forEach((x) => { // xValues.push(x); // }); // Array.from(yVals).forEach((y) => { // yValues.push(y); // }); } // const segmentCoeffs = computeAkimaPolyCoefficients(xVals, yVals); // const xValsCopy = Float64Array.from(xVals); // clone to break dependency on passed values // return (x: number) => evaluatePolySegment(xValsCopy, segmentCoeffs, x); const segmentCoeffs = computeAkimaPolyCoefficients(xValues, yValues); const xValsCopy = Float64Array.from(xValues); // clone to break dependency on passed values return (x) => evaluatePolySegment(xValsCopy, segmentCoeffs, x); } /** * Computes the polynomial coefficients for the Akima cubic spline * interpolation of a dataset. * * @param xVals * The arguments of the interpolation points, in strictly increasing order. * @param yVals * The values of the interpolation points. * @returns * Polynomial coefficients of the segments. */ function computeAkimaPolyCoefficients(xVals, yVals) { if (xVals.length !== yVals.length) { throw new Error('Dimension mismatch for xVals and yVals.'); } if (xVals.length < 5) { throw new Error('Number of points is too small.'); } checkStrictlyIncreasing(xVals); const n = xVals.length - 1; // number of segments const differences = new Float64Array(n); const weights = new Float64Array(n); for (let i = 0; i < n; i++) { differences[i] = (yVals[i + 1] - yVals[i]) / (xVals[i + 1] - xVals[i]); } for (let i = 1; i < n; i++) { weights[i] = Math.abs(differences[i] - differences[i - 1]); } // Prepare Hermite interpolation scheme. const firstDerivatives = new Float64Array(n + 1); for (let i = 2; i < n - 1; i++) { const wP = weights[i + 1]; const wM = weights[i - 1]; if (Math.abs(wP) < EPSILON && Math.abs(wM) < EPSILON) { const xv = xVals[i]; const xvP = xVals[i + 1]; const xvM = xVals[i - 1]; firstDerivatives[i] = ((xvP - xv) * differences[i - 1] + (xv - xvM) * differences[i]) / (xvP - xvM); } else { firstDerivatives[i] = (wP * differences[i - 1] + wM * differences[i]) / (wP + wM); } } firstDerivatives[0] = differentiateThreePoint(xVals, yVals, 0, 0, 1, 2); firstDerivatives[1] = differentiateThreePoint(xVals, yVals, 1, 0, 1, 2); firstDerivatives[n - 1] = differentiateThreePoint(xVals, yVals, n - 1, n - 2, n - 1, n); firstDerivatives[n] = differentiateThreePoint(xVals, yVals, n, n - 2, n - 1, n); return computeHermitePolyCoefficients(xVals, yVals, firstDerivatives); } /** * Computes the polynomial coefficients for the Hermite cubic spline interpolation * for a set of (x,y) value pairs and their derivatives. This is modeled off of * the InterpolateHermiteSorted method in the Math.NET CubicSpline class. * * @param xVals * x values for interpolation. * @param yVals * y values for interpolation. * @param firstDerivatives * First derivative values of the function. * @returns * Polynomial coefficients of the segments. */ function computeHermitePolyCoefficients(xVals, yVals, firstDerivatives) { if (xVals.length !== yVals.length || xVals.length !== firstDerivatives.length) { throw new Error('Dimension mismatch'); } if (xVals.length < 2) { throw new Error('Not enough points.'); } const n = xVals.length - 1; // number of segments const segmentCoeffs = new Array(n); for (let i = 0; i < n; i++) { const w = xVals[i + 1] - xVals[i]; const w2 = w * w; const yv = yVals[i]; const yvP = yVals[i + 1]; const fd = firstDerivatives[i]; const fdP = firstDerivatives[i + 1]; const coeffs = new Float64Array(4); coeffs[0] = yv; coeffs[1] = firstDerivatives[i]; coeffs[2] = ((3 * (yvP - yv)) / w - 2 * fd - fdP) / w; coeffs[3] = ((2 * (yv - yvP)) / w + fd + fdP) / w2; segmentCoeffs[i] = trimPoly(coeffs); } return segmentCoeffs; } /** * Three point differentiation helper, modeled off of the same method in the * Math.NET CubicSpline class. * * @param xVals * x values to calculate the numerical derivative with. * @param yVals * y values to calculate the numerical derivative with. * @param indexOfDifferentiation * Index of the elemnt we are calculating the derivative around. * @param indexOfFirstSample * Index of the first element to sample for the three point method. * @param indexOfSecondsample * index of the second element to sample for the three point method. * @param indexOfThirdSample * Index of the third element to sample for the three point method. * @returns * The derivative. */ function differentiateThreePoint(xVals, yVals, indexOfDifferentiation, indexOfFirstSample, indexOfSecondsample, indexOfThirdSample) { const x0 = yVals[indexOfFirstSample]; const x1 = yVals[indexOfSecondsample]; const x2 = yVals[indexOfThirdSample]; const t = xVals[indexOfDifferentiation] - xVals[indexOfFirstSample]; const t1 = xVals[indexOfSecondsample] - xVals[indexOfFirstSample]; const t2 = xVals[indexOfThirdSample] - xVals[indexOfFirstSample]; const a = (x2 - x0 - (t2 / t1) * (x1 - x0)) / (t2 * t2 - t1 * t2); const b = (x1 - x0 - a * t1 * t1) / t1; return 2 * a * t + b; }