/** * @file Interpolation.js * * Permission is hereby granted to any person obtaining a copy of this software * and associated documentation files (the "Software"), to use it for personal * or non-commercial purposes, with the following restrictions: * * 1. **No Copying or Redistribution**: The Software or any of its parts may not * be copied, merged, distributed, sublicensed, or sold without explicit * prior written permission from the author. * * 2. **Commercial Use**: Any use of the Software for commercial purposes requires * a valid license, obtainable only with the explicit consent of the author. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, * OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Ownership of this code remains solely with the original author. Unauthorized * use of this Software is strictly prohibited. * * Author: * - Rene De Ren * Email: * - rene@thegoldenbasket.nl * /* Interpolate using cubic Hermite splines. The breakpoints in arrays xbp and ybp are assumed to be sorted. Evaluate the function in all points of the array xeval. Methods: "Linear" yuck "FiniteDifference" classic cubic interpolation, no tension parameter "Cardinal" cubic cardinal splines, uses tension parameter which must be between [0,1] "FritschCarlson" monotonic - tangents are first initialized, then adjusted if they are not monotonic "FritschButland" monotonic - faster algorithm () but somewhat higher apparent "tension" "Steffen" monotonic - also only one pass, results usualonly requires one passly between FritschCarlson and FritschButland Sources: Fritsch & Carlson (1980), "Monotone Piecewise Cubic Interpolation", doi:10.1137/0717021. Fritsch & Butland (1984), "A Method for Constructing Local Monotone Piecewise Cubic Interpolants", doi:10.1137/0905021. Steffen (1990), "A Simple Method for Monotonic Interpolation in One Dimension", http://adsabs.harvard.edu/abs/1990A%26A...239..443S Year : (c) 2023 Author : Rene De Ren Contact details : zn375ix3@gmail.com Location : The Netherlands */ class Interpolation { constructor(config = {}) { this.input_xdata = []; this.input_ydata = []; this.y2 = []; this.n = 0; this.error = 0; this.interpolationtype = config.type || "monotone_cubic_spline"; this.tension = config.tension || 0.5; } load_spline(input_xdata, input_ydata, interpolationtype) { if (!Array.isArray(input_xdata) || !Array.isArray(input_ydata)) { throw new Error("Invalid input: x and y must be arrays"); } if (input_xdata.length !== input_ydata.length) { throw new Error("Arrays x and y must have the same length"); } if (input_xdata.length < 2) { throw new Error("Arrays must contain at least 2 points for interpolation"); } for (let i = 1; i < input_xdata.length; i++) { if (input_xdata[i] <= input_xdata[i - 1]) { throw new Error("X values must be strictly increasing"); } } this.input_xdata = this.array_values(input_xdata); this.input_ydata = this.array_values(input_ydata); this.set_type(interpolationtype); } array_values(obj) { const new_array = []; for (let i in obj) { if (obj.hasOwnProperty(i)) { new_array.push(obj[i]); } } return new_array; } set_type(type) { if (type == "cubic_spline") { this.cubic_spline(); } else if (type == "monotone_cubic_spline") { this.monotonic_cubic_spline(); } else if (type == "linear") { } else { this.error = 1000; } this.interpolationtype = type; } interpolate(xpoint) { if (!this.input_xdata || !this.input_ydata || this.input_xdata.length < 2) { throw new Error("Spline not properly initialized"); } if (xpoint <= this.input_xdata[0]) return this.input_ydata[0]; if (xpoint >= this.input_xdata[this.input_xdata.length - 1]) return this.input_ydata[this.input_ydata.length - 1]; let interpolatedval = 0; if (this.interpolationtype == "cubic_spline") { interpolatedval = this.interpolate_cubic(xpoint); } else if (this.interpolationtype == "monotone_cubic_spline") { interpolatedval = this.interpolate_cubic_monotonic(xpoint); } else if (this.interpolationtype == "linear") { interpolatedval = this.linear(xpoint); } else { console.log(this.interpolationtype); interpolatedval = "Unknown type"; } return interpolatedval; } cubic_spline() { var xdata = this.input_xdata; var ydata = this.input_ydata; var delta = []; var n = ydata.length; this.n = n; if (n !== xdata.length) { this.error = 1; } this.y2[0] = 0.0; this.y2[n - 1] = 0.0; delta[0] = 0.0; for (let i = 1; i < n - 1; ++i) { let d = xdata[i + 1] - xdata[i - 1]; if (d == 0) { this.error = 2; } let s = (xdata[i] - xdata[i - 1]) / d; let p = s * this.y2[i - 1] + 2.0; this.y2[i] = (s - 1.0) / p; delta[i] = (ydata[i + 1] - ydata[i]) / (xdata[i + 1] - xdata[i]) - (ydata[i] - ydata[i - 1]) / (xdata[i] - xdata[i - 1]); delta[i] = (6.0 * delta[i]) / (xdata[i + 1] - xdata[i - 1]) - (s * delta[i - 1]) / p; } for (let j = n - 2; j >= 0; --j) { this.y2[j] = this.y2[j] * this.y2[j + 1] + delta[j]; } } linear(xpoint) { var i_min = 0; var i_max = 0; var o_min = 0; var o_max = 0; for (let i = 0; i < this.input_xdata.length; i++) { if (xpoint >= this.input_xdata[i] && xpoint < this.input_xdata[i + 1]) { i_min = this.input_xdata[i]; i_max = this.input_xdata[i + 1]; o_min = this.input_ydata[i]; o_max = this.input_ydata[i + 1]; break; } } let o_number; if (i_min < i_max) { o_number = o_min + ((xpoint - i_min) * (o_max - o_min)) / (i_max - i_min); } else { o_number = xpoint; } return o_number; } interpolate_cubic(xpoint) { let xdata = this.input_xdata; let ydata = this.input_ydata; let max = this.n - 1; let min = 0; while (max - min > 1) { let k = Math.floor((max + min) / 2); if (xdata[k] > xpoint) max = k; else min = k; } let h = xdata[max] - xdata[min]; if (h == 0) { this.error = 3; } let a = (xdata[max] - xpoint) / h; let b = (xpoint - xdata[min]) / h; let interpolatedvalue = a * ydata[min] + b * ydata[max] + ((a * a * a - a) * this.y2[min] + (b * b * b - b) * this.y2[max]) * (h * h) / 6.0; return interpolatedvalue; } monotonic_cubic_spline() { let xdata = this.input_xdata; let ydata = this.input_ydata; let interpolationtype = this.interpolationtype; let tension = this.tension; let n = ydata.length; this.n = n; if (this.n !== xdata.length) { this.error = 1; } let obj = this.calc_tangents(xdata, ydata, tension); this.y1 = obj[0]; this.delta = obj[1]; } interpolate_cubic_monotonic(xpoint) { let xdata = this.input_xdata; let ydata = this.input_ydata; let xinterval = 0; let y1 = this.y1; let delta = this.delta; let c = []; let d = []; let n = this.n; for (let k = 0; k < n - 1; k++) { xinterval = xdata[k + 1] - xdata[k]; c[k] = (3 * delta[k] - 2 * y1[k] - y1[k + 1]) / xinterval; d[k] = (y1[k] + y1[k + 1] - 2 * delta[k]) / xinterval / xinterval; } let interpolatedvalues = []; let k = 0; if (xpoint < xdata[0] || xpoint > xdata[n - 1]) { } while (k < n - 1 && xpoint > xdata[k + 1] && !(xpoint < xdata[0] || xpoint > xdata[n - 1])) { k++; } let xdiffdown = xpoint - xdata[k]; interpolatedvalues = ydata[k] + y1[k] * xdiffdown + c[k] * xdiffdown * xdiffdown + d[k] * xdiffdown * xdiffdown * xdiffdown; return interpolatedvalues; } calc_tangents(xdata, ydata, tension) { let method = this.interpolationtype; let n = xdata.length; let delta_array = []; let delta = 0; let y1 = []; for (let i = 0; i < n - 1; i++) { delta = (ydata[i + 1] - ydata[i]) / (xdata[i + 1] - xdata[i]); delta_array[i] = delta; if (i == 0) { y1[i] = delta; } else if (method == "cardinal") { y1[i] = (1 - tension) * (ydata[i + 1] - ydata[i - 1]) / (xdata[i + 1] - xdata[i - 1]); } else if (method == "fritschbutland") { let alpha = (1 + (xdata[i + 1] - xdata[i]) / (xdata[i + 1] - xdata[i - 1])) / 3; y1[i] = delta_array[i - 1] * delta <= 0 ? 0 : (delta_array[i - 1] * delta) / (alpha * delta + (1 - alpha) * delta_array[i - 1]); } else if (method == "fritschcarlson") { y1[i] = delta_array[i - 1] * delta < 0 ? 0 : (delta_array[i - 1] + delta) / 2; } else if (method == "steffen") { let p = ((xdata[i + 1] - xdata[i]) * delta_array[i - 1] + (xdata[i] - xdata[i - 1]) * delta) / (xdata[i + 1] - xdata[i - 1]); y1[i] = (Math.sign(delta_array[i - 1]) + Math.sign(delta)) * Math.min(Math.abs(delta_array[i - 1]), Math.abs(delta), 0.5 * Math.abs(p)); } else { y1[i] = (delta_array[i - 1] + delta) / 2; } } y1[n - 1] = delta_array[n - 2]; if (method != "fritschcarlson") { return [y1, delta_array]; } for (let i = 0; i < n - 1; i++) { let delta = delta_array[i]; if (delta == 0) { y1[i] = 0; y1[i + 1] = 0; continue; } let alpha = y1[i] / delta; let beta = y1[i + 1] / delta; let tau = 3 / Math.sqrt(Math.pow(alpha, 2) + Math.pow(beta, 2)); if (tau < 1) { y1[i] = tau * alpha * delta; y1[i + 1] = tau * beta * delta; } } return [y1, delta_array]; } interpolate_lin_curve_points(i_curve, o_min, o_max) { if (!Array.isArray(i_curve)) { throw new Error("xArray must be an array"); } let o_curve = {}; let i_min = 0; let i_max = 0; i_min = Math.min(...Object.values(i_curve)); i_max = Math.max(...Object.values(i_curve)); i_curve.forEach((val, index) => { o_curve[index] = this.interpolate_lin_single_point(val, i_min, i_max, o_min, o_max); }); o_curve = Object.values(o_curve); return o_curve; } interpolate_lin_single_point(i_number, i_min, i_max, o_min, o_max) { if (typeof i_number !== "number" || typeof i_min !== "number" || typeof i_max !== "number" || typeof o_min !== "number" || typeof o_max !== "number") { throw new Error("All parameters must be numbers"); } if (i_max === i_min) { return o_min; } let o_number; //i_number = this.limit_input(i_number, i_min, i_max); o_number = o_min + ((i_number - i_min) * (o_max - o_min)) / (i_max - i_min); o_number = this.limit_input(o_number, o_min, o_max); return o_number; } limit_input(input, min, max) { let output; if (input < min) { output = min; } else if (input > max) { output = max; } else { output = input; } return output; } } module.exports = Interpolation;