commons-math-interpolation
Version:
A partial port of the Apache Commons Math Interpolation package, including Akima cubic spline interpolation and LOESS/LOWESS local regression.
81 lines • 3.67 kB
JavaScript
import { checkStrictlyIncreasing, trimPoly, evaluatePolySegment } from "./Utils.js";
const EPSILON = Number.EPSILON;
export function createAkimaSplineInterpolator(xVals, yVals) {
const segmentCoeffs = computeAkimaPolyCoefficients(xVals, yVals);
const xValsCopy = Float64Array.from(xVals);
return (x) => evaluatePolySegment(xValsCopy, segmentCoeffs, x);
}
export 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;
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]);
}
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);
}
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;
}
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;
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;
}
//# sourceMappingURL=Akima.js.map