Updates for machine
This commit is contained in:
387
src/predict/interpolation.js
Normal file
387
src/predict/interpolation.js
Normal file
@@ -0,0 +1,387 @@
|
||||
/**
|
||||
* @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;
|
||||
Reference in New Issue
Block a user