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;
|
||||
199
src/predict/predictConfig.json
Normal file
199
src/predict/predictConfig.json
Normal file
@@ -0,0 +1,199 @@
|
||||
{
|
||||
"general": {
|
||||
"name": {
|
||||
"default": "Interpolation Configuration",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "A human-readable name or label for this interpolation configuration."
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"default": null,
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "A unique identifier for this configuration. If not provided, defaults to null."
|
||||
}
|
||||
},
|
||||
"unit": {
|
||||
"default": "unitless",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "The unit used for the interpolated values (e.g., 'meters', 'seconds', 'unitless')."
|
||||
}
|
||||
},
|
||||
"logging": {
|
||||
"logLevel": {
|
||||
"default": "info",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "debug",
|
||||
"description": "Log messages are printed for debugging purposes."
|
||||
},
|
||||
{
|
||||
"value": "info",
|
||||
"description": "Informational messages are printed."
|
||||
},
|
||||
{
|
||||
"value": "warn",
|
||||
"description": "Warning messages are printed."
|
||||
},
|
||||
{
|
||||
"value": "error",
|
||||
"description": "Error messages are printed."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"enabled": {
|
||||
"default": true,
|
||||
"rules": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates whether logging is active. If true, log messages will be generated."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"functionality": {
|
||||
"softwareType": {
|
||||
"default": "interpolation",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "interpolation",
|
||||
"description": "Specifies this component as an interpolation engine."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"default": "Interpolator",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "Indicates the role of this configuration (e.g., 'Interpolator', 'DataCurve', etc.)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"interpolation": {
|
||||
"enabled": {
|
||||
"default": true,
|
||||
"rules": {
|
||||
"type": "boolean",
|
||||
"description": "Flag to enable/disable interpolation."
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"default": "monotone_cubic_spline",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "cubic_spline",
|
||||
"description": "Standard cubic spline interpolation (natural boundary)."
|
||||
},
|
||||
{
|
||||
"value": "monotone_cubic_spline",
|
||||
"description": "Monotonic cubic spline interpolation (e.g., Fritsch-Carlson)."
|
||||
},
|
||||
{
|
||||
"value": "linear",
|
||||
"description": "Basic linear interpolation between data points."
|
||||
}
|
||||
],
|
||||
"description": "Specifies the default interpolation method."
|
||||
}
|
||||
},
|
||||
"tension": {
|
||||
"default": 0.5,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"description": "Tension parameter (0–1) for spline methods like 'cardinal'."
|
||||
}
|
||||
}
|
||||
},
|
||||
"normalization": {
|
||||
"enabled": {
|
||||
"default": true,
|
||||
"rules": {
|
||||
"type": "boolean",
|
||||
"description": "Flag to enable/disable normalization of input data."
|
||||
}
|
||||
},
|
||||
"normalizationType": {
|
||||
"default": "minmax",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "minmax",
|
||||
"description": "Min-max normalization (default)."
|
||||
},
|
||||
{
|
||||
"value": "zscore",
|
||||
"description": "Z-score normalization."
|
||||
}
|
||||
],
|
||||
"description": "Specifies the type of normalization to apply."
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"default": {},
|
||||
"rules": {
|
||||
"type": "object",
|
||||
"schema": {
|
||||
"min": {
|
||||
"default": 0,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "Minimum value for normalization."
|
||||
}
|
||||
},
|
||||
"max": {
|
||||
"default": 1000,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "Maximum value for normalization."
|
||||
}
|
||||
},
|
||||
"curvePoints": {
|
||||
"default": 10,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "Number of points in the normalization curve."
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Normalization parameters (e.g., 'min', 'max', 'mean', 'std')."
|
||||
}
|
||||
}
|
||||
},
|
||||
"curve": {
|
||||
"default": {
|
||||
"1": {
|
||||
"x": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"y": [
|
||||
10,
|
||||
20,
|
||||
30,
|
||||
40,
|
||||
50
|
||||
]
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"type": "curve",
|
||||
"description": "Explicitly enumerated dimension keys (no wildcard)."
|
||||
}
|
||||
}
|
||||
}
|
||||
593
src/predict/predict_class.js
Normal file
593
src/predict/predict_class.js
Normal file
@@ -0,0 +1,593 @@
|
||||
/**
|
||||
* @file Predict_class.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.
|
||||
*
|
||||
* @summary Class for predicting values based on a multidimensional curve.
|
||||
* @description Class for predicting values based on a multidimensional curve.
|
||||
* @module Predict_class
|
||||
* @requires EventEmitter
|
||||
* @requires ConfigUtils
|
||||
* @requires Interpolation
|
||||
* @requires Logger
|
||||
* @exports Predict
|
||||
* @version 0.1.0
|
||||
* @since 0.1.0
|
||||
*
|
||||
* Author:
|
||||
* - Rene De Ren
|
||||
* Email:
|
||||
* - rene@thegoldenbasket.nl
|
||||
* Future Improvements:
|
||||
- Add more interpolation types
|
||||
- **Local Derivative (Slope)**: Instantaneous rate of change (dY/dX) at the current X. Useful for determining if the curve is ascending or descending.
|
||||
- **Second Derivative (Curvature)**: Curvature (d²Y/dX²) at the current X. Indicates how quickly the slope is changing (e.g., sharp or broad peaks).
|
||||
- **Distance to Nearest Local Peak or Valley**: X-distance from the current X to the closest local maximum or minimum. Useful for detecting proximity to turning points.
|
||||
- **Global Statistics (Mean, Median, Std Dev)**:
|
||||
- Mean: Average of Y.
|
||||
- Median: Middle Y value (sorted).
|
||||
- Std Dev: Variability of Y. Provides insight into central tendency and spread, aiding in normalization or anomaly detection.
|
||||
- **Integrated Area Under the Curve (AUC)**: Numerical integration of Y across the X-range. Useful for total sums or energy-related calculations.
|
||||
- **Peak “Sharpness” or “Prominence”**: Measure of a peak's height and width relative to surrounding valleys. Important for signal processing or optimization.
|
||||
- **Nearest Points Around Current X**: Data points (or interpolated values) immediately to the left and right of the current X. Useful for local interpolation or neighbor analysis.
|
||||
- **Forecast / Extrapolation**: Estimated Y values outside the known X-range. Useful for exploring scenarios slightly beyond the data range (use with caution).
|
||||
- **Peak Count**: Total number of local maxima in the curve. Useful for identifying all peaks and their prominence.
|
||||
- **Position Relative to Mean (or Other Reference Lines)**: Distance (in percent or absolute value) of the current Y from a reference line (e.g., mean or median). Provides context relative to average or baseline levels.
|
||||
- **Local Slope Trend**: Direction of the slope (up, down, or flat) at the current X. Useful for identifying trends or inflection points.
|
||||
- **Local Curvature Trend**: Direction of the curvature (concave up, concave down, or flat) at the current X. Useful for identifying inflection points or turning points.
|
||||
- **Local Peak-to-Valley Ratio**: Ratio of the current peak height to the nearest valley depth. Useful for identifying peak prominence or sharpness.
|
||||
- ** Keep track of previous request and next request to identify slope and curvature
|
||||
*/
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const Logger = require('../helper/logger.js');
|
||||
const defaultConfig = require('./predictConfig.json');
|
||||
const ConfigUtils = require('../helper/configUtils');
|
||||
const Interpolation = require('./interpolation');
|
||||
|
||||
class Predict {
|
||||
constructor(config = {}) {
|
||||
|
||||
// Initialize dependencies
|
||||
this.emitter = new EventEmitter(); // Own EventEmitter
|
||||
this.configUtils = new ConfigUtils(defaultConfig);
|
||||
this.config = this.configUtils.initConfig(config);
|
||||
|
||||
// Init after config is set
|
||||
this.logger = new Logger(this.config.general.logging.enabled,this.config.general.logging.logLevel, this.config.general.name);
|
||||
this.interpolation = new Interpolation(this.config.interpolation);
|
||||
|
||||
// Input and state
|
||||
this.inputCurve = {};
|
||||
this.currentF = 0;
|
||||
this.currentX = 0;
|
||||
this.outputY = 0;
|
||||
|
||||
// Curves and Splines
|
||||
this.normalizedCurve = {};
|
||||
this.calculatedCurve = {};
|
||||
this.fCurve = {};
|
||||
this.currentFxyCurve = {};
|
||||
this.normalizedSplines = {};
|
||||
this.fSplines = {};
|
||||
this.currentFxySplines = {};
|
||||
|
||||
// Stored min/max values
|
||||
this.xValues = {};
|
||||
this.fValues = {};
|
||||
this.yValues = {};
|
||||
this.currentFxyXMin = 0;
|
||||
this.currentFxyXMax = 0;
|
||||
this.currentFxyYMin = 0;
|
||||
this.currentFxyYMax = 0;
|
||||
|
||||
// From config
|
||||
this.normMin = this.config.normalization.parameters.min;
|
||||
this.normMax = this.config.normalization.parameters.max;
|
||||
this.calculationPoints = this.config.normalization.parameters.curvePoints;
|
||||
this.interpolationType = this.config.interpolation.type;
|
||||
|
||||
// Load curve if provided
|
||||
if (config.curve) {
|
||||
this.inputCurveData = config.curve;
|
||||
} else {
|
||||
this.logger.warn("No curve data provided. Please set curve data using setCurveData method. Using default");
|
||||
this.inputCurveData = this.config.curve;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Improved function to get a local peak in an array by starting in the middle.
|
||||
// It also handles the case of a tie by preferring the left side (arbitrary choice)
|
||||
// when array[start] == leftValue or array[start] == rightValue.
|
||||
getLocalPeak(array) {
|
||||
if (!Array.isArray(array) || array.length === 0) {
|
||||
return { peak: null, peakIndex: -1 };
|
||||
}
|
||||
|
||||
let left = 0;
|
||||
let right = array.length - 1;
|
||||
|
||||
while (left <= right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
|
||||
// Safely retrieve left/right neighbor values (use -Infinity if out of bounds)
|
||||
const leftVal = mid - 1 >= 0 ? array[mid - 1] : -Infinity;
|
||||
const rightVal = mid + 1 < array.length ? array[mid + 1] : -Infinity;
|
||||
const currentVal = array[mid];
|
||||
|
||||
// Check if mid is a local peak
|
||||
if (currentVal >= leftVal && currentVal >= rightVal) {
|
||||
return { peak: currentVal, peakIndex: mid };
|
||||
}
|
||||
|
||||
// If left neighbor is bigger, move left
|
||||
if (leftVal > currentVal) {
|
||||
right = mid - 1;
|
||||
}
|
||||
// Otherwise, move right
|
||||
else {
|
||||
left = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If no local peak is found
|
||||
return { peak: null, peakIndex: -1 };
|
||||
}
|
||||
|
||||
// Function what uses the peak in the y array to return the yPeak, x value and its procentual value
|
||||
getPosXofYpeak(curve) {
|
||||
|
||||
//find index of y peak
|
||||
const { peak , peakIndex } = this.getLocalPeak(curve.y);
|
||||
|
||||
// scale the x value to procentual value
|
||||
const yPeak = peak;
|
||||
const x = curve.x[peakIndex];
|
||||
const xMin = Math.min(...curve.x);
|
||||
const xMax = Math.max(...curve.x);
|
||||
const xProcent = (x - xMin) / (xMax - xMin) * 100;
|
||||
|
||||
return { yPeak, x, xProcent };
|
||||
}
|
||||
|
||||
calcRelativePositionToPeak(curve , outputY) {
|
||||
|
||||
//find y peak
|
||||
const { peak } = this.getLocalPeak(curve.y);
|
||||
|
||||
if ( peak === null ) {
|
||||
this.logger.warn("No peak found in curve");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calculate the "peak-only" percentage:
|
||||
// - Distance from peak, relative to peak itself
|
||||
// - 0% => outputY == peak, 100% => outputY == 0 (if peak != 0)
|
||||
let peakOnlyPercentage;
|
||||
const distanceFromPeak = Math.abs(peak - outputY);
|
||||
if (peak === 0) {
|
||||
// If peak is 0, then the concept of "peak-only" percentage is tricky.
|
||||
// If outputY is also 0 => 0%, otherwise => Infinity.
|
||||
peakOnlyPercentage = distanceFromPeak === 0 ? 0 : Number.POSITIVE_INFINITY;
|
||||
} else {
|
||||
peakOnlyPercentage = (distanceFromPeak / peak) * 100;
|
||||
}
|
||||
|
||||
// Calculate the range-based percentage:
|
||||
// - Range = [yMin, peak]
|
||||
// - 0% => outputY == peak, 100% => outputY == yMin
|
||||
const yMin = Math.min(...curve.y);
|
||||
let rangeBasedPercentage = -1;
|
||||
|
||||
// If peak <= yMin, there is no vertical range for normalization
|
||||
if (peak > yMin) {
|
||||
const distanceFromPeakRange = peak - outputY; // Not absolute
|
||||
const totalRange = peak - yMin;
|
||||
rangeBasedPercentage = (distanceFromPeakRange / totalRange) * 100;
|
||||
|
||||
// Optionally clamp to [0, 100] if outputY goes out of bounds
|
||||
rangeBasedPercentage = Math.max(0, Math.min(100, rangeBasedPercentage));
|
||||
}
|
||||
|
||||
return {
|
||||
peakOnlyPercentage: Math.round(peakOnlyPercentage * 100) / 100,
|
||||
rangeBasedPercentage: Math.round(rangeBasedPercentage * 100) / 100
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Function to retrieve current curve including the interpolated active point
|
||||
retrieveActiveCurve(){
|
||||
|
||||
// Retreive y values
|
||||
const yValues = this.currentFxyCurve[this.fDimension].y;
|
||||
// Retreive normalized x values
|
||||
const xValues = this.denormalizeXvals( this.currentFxyCurve[this.fDimension].x );
|
||||
|
||||
//check what the current x value is
|
||||
const currentX = this.currentX;
|
||||
|
||||
//check current y Output value
|
||||
const outputY = this.outputY;
|
||||
|
||||
//find where the current x value should be in the xValues array
|
||||
const index = xValues.findIndex((x) => x > currentX);
|
||||
|
||||
// push the yOutput value in the yValues array between the current x value
|
||||
yValues.splice(index, 0, outputY);
|
||||
xValues.splice(index, 0, currentX);
|
||||
|
||||
return { xValues, yValues };
|
||||
}
|
||||
|
||||
set fDimension(newF) {
|
||||
|
||||
if (newF < this.fValues.min || newF > this.fValues.max) {
|
||||
this.logger.warn(`New f =${newF} is constrained to fit between min=${this.fValues.min} and max=${this.fValues.max}`);
|
||||
newF = this.constrain(newF,this.fValues.min,this.fValues.max);
|
||||
}
|
||||
|
||||
if (newF in this.calculatedCurve) {
|
||||
this.currentFxyCurve[newF] = this.calculatedCurve[newF];
|
||||
this.currentFxySplines = this.normalizedSplines;
|
||||
} else {
|
||||
this.currentFxyCurve = this.buildSingleFxyCurve(
|
||||
this.fSplines,
|
||||
this.calculatedCurve,
|
||||
newF,
|
||||
this.calculationPoints
|
||||
);
|
||||
this.currentFxySplines = this.buildXySplines(this.currentFxyCurve, this.interpolationType);
|
||||
}
|
||||
|
||||
const yArray = this.currentFxyCurve[newF].y;
|
||||
this.currentFxyYMin = Math.min(...yArray);
|
||||
this.currentFxyYMax = Math.max(...yArray);
|
||||
|
||||
this.calculateFxyXRange(newF);
|
||||
|
||||
this.currentF = newF;
|
||||
this.logger.debug(`Calculating new yValue using X= ${this.currentX}`);
|
||||
|
||||
// Recalculate output y based on currentX
|
||||
this.y(this.currentX);
|
||||
|
||||
}
|
||||
|
||||
get fDimension() {
|
||||
return this.currentF;
|
||||
}
|
||||
|
||||
// Function to predict Y value based on X value
|
||||
y(x) {
|
||||
|
||||
// Clamp value before normalization
|
||||
if (x > this.currentFxyXMax) x = this.currentFxyXMax;
|
||||
if (x < this.currentFxyXMin) x = this.currentFxyXMin;
|
||||
|
||||
//keep track of current x value
|
||||
this.currentX = x;
|
||||
|
||||
this.logger.debug(`Interpolating x using input=${x} , currentFxyXmin=${this.currentFxyXMin}, currentFxyXMax=${this.currentFxyXMax}, normMin=${this.normMin}, normMax=${this.normMax} `);
|
||||
|
||||
const normalizedX = this.interpolation.interpolate_lin_single_point(
|
||||
x,
|
||||
this.currentFxyXMin,
|
||||
this.currentFxyXMax,
|
||||
this.normMin,
|
||||
this.normMax
|
||||
);
|
||||
|
||||
this.logger.debug(`Calculating new Y value using ${normalizedX}`);
|
||||
|
||||
this.outputY = this.currentFxySplines[this.fDimension].interpolate(normalizedX);
|
||||
|
||||
return this.outputY;
|
||||
|
||||
}
|
||||
|
||||
set yOutput(y) {
|
||||
this.outputY = y;
|
||||
//by emitting this one output we dont have to use the entire class
|
||||
this.emitter.emit('yOutput', this.outputY);
|
||||
}
|
||||
|
||||
get yOutput() {
|
||||
return this.outputY;
|
||||
}
|
||||
|
||||
set inputCurveData(curve) {
|
||||
try {
|
||||
this.inputCurve = curve;
|
||||
this.buildAllFxyCurves(curve);
|
||||
} catch (error) {
|
||||
this.logger.error(`Curve validation failed: ${error.message}`);
|
||||
this.inputCurve = null; // Reset curve data if validation fails
|
||||
}
|
||||
}
|
||||
|
||||
get inputCurveData() {
|
||||
return this.inputCurve;
|
||||
}
|
||||
|
||||
updateCurve(curve) {
|
||||
|
||||
this.logger.info("Updating curve data");
|
||||
// update config with new curve data merged with existing config
|
||||
const newConfig = {...this.config, curve: curve};
|
||||
this.config = this.configUtils.updateConfig(newConfig);
|
||||
|
||||
const validatedCurve = this.config.curve;
|
||||
this.inputCurve = validatedCurve;
|
||||
|
||||
this.buildAllFxyCurves(validatedCurve);
|
||||
|
||||
}
|
||||
|
||||
constrain(value,min,max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
buildAllFxyCurves(curve) {
|
||||
let globalMinY = Infinity;
|
||||
let globalMaxY = -Infinity;
|
||||
|
||||
for (const fKey of Object.keys(curve)) {
|
||||
const f = Number(fKey);
|
||||
this.xValues[f] = {
|
||||
min: Math.min(...curve[f].x),
|
||||
max: Math.max(...curve[f].x),
|
||||
};
|
||||
|
||||
const fMinY = Math.min(...curve[f].y);
|
||||
const fMaxY = Math.max(...curve[f].y);
|
||||
|
||||
if (fMinY < globalMinY) globalMinY = fMinY;
|
||||
if (fMaxY > globalMaxY) globalMaxY = fMaxY;
|
||||
|
||||
// Normalize curves
|
||||
this.normalizedCurve[f] = this.normalizeCurve(curve[f], this.normMin, this.normMax);
|
||||
|
||||
}
|
||||
|
||||
this.normalizedSplines = this.buildXySplines(this.normalizedCurve, this.interpolationType);
|
||||
|
||||
// Build calculated curves (same #points across all f)
|
||||
for (const f of Object.keys(this.normalizedCurve)) {
|
||||
this.calculatedCurve[f] = this.buildCalculatedCurve(this.normalizedSplines, f, this.calculationPoints);
|
||||
}
|
||||
|
||||
this.fCurve = this.buildFCurve(this.calculatedCurve, this.calculationPoints);
|
||||
this.fSplines = this.buildFSplines(this.fCurve, this.interpolationType);
|
||||
|
||||
const fKeys = Object.keys(curve).map(Number);
|
||||
this.fValues.min = Math.min(...fKeys);
|
||||
this.fValues.max = Math.max(...fKeys);
|
||||
|
||||
this.yValues.lowest = globalMinY;
|
||||
this.yValues.highest = globalMaxY;
|
||||
|
||||
// Set initial fDimension to min
|
||||
this.fDimension = this.fValues.min;
|
||||
this.logger.debug(` !!! Initial fDimension set to ${this.fValues.min}`);
|
||||
}
|
||||
|
||||
normalizeVal(val, normMin, normMax) {
|
||||
return this.interpolation.interpolate_lin_single_point(val, normMin, normMax, 1, this.calculationPoints);
|
||||
}
|
||||
|
||||
normalizeCurve(curve, normMin, normMax) {
|
||||
return {
|
||||
x: this.interpolation.interpolate_lin_curve_points(curve.x, normMin, normMax),
|
||||
y: curve.y,
|
||||
};
|
||||
}
|
||||
|
||||
denormalizeXvals(xValues) {
|
||||
// Retrieve the normalized x-array from the current Fxy curve
|
||||
const normalizedX = xValues;
|
||||
|
||||
// Map each normalized x to its denormalized value
|
||||
const denormalizedX = normalizedX.map(nx => {
|
||||
return this.interpolation.interpolate_lin_single_point(
|
||||
nx,
|
||||
this.normMin,
|
||||
this.normMax,
|
||||
this.currentFxyXMin,
|
||||
this.currentFxyXMax
|
||||
);
|
||||
});
|
||||
|
||||
// Return a new object with denormalized x and the original y array
|
||||
return denormalizedX;
|
||||
}
|
||||
|
||||
// interpolate input x value to denormalized x value
|
||||
denormalizeX(x) {
|
||||
return this.interpolation.interpolate_lin_single_point(
|
||||
x,
|
||||
this.normMin,
|
||||
this.normMax,
|
||||
this.currentFxyXMin,
|
||||
this.currentFxyXMax
|
||||
);
|
||||
}
|
||||
|
||||
buildCalculatedCurve(splines, f, pointsCount) {
|
||||
const cCurve = { x: [], y: [] };
|
||||
for (let i = 1; i <= pointsCount; i++) {
|
||||
const nx = this.interpolation.interpolate_lin_single_point(i, 1, pointsCount, this.normMin, this.normMax);
|
||||
cCurve.x.push(nx);
|
||||
cCurve.y.push(splines[f].interpolate(nx));
|
||||
}
|
||||
return cCurve;
|
||||
}
|
||||
|
||||
buildFCurve(curve, pointsCount) {
|
||||
const fCurve = {};
|
||||
for (let i = 0; i < pointsCount; i++) {
|
||||
fCurve[i] = { x: [], y: [] };
|
||||
}
|
||||
|
||||
for (let i = 0; i < pointsCount; i++) {
|
||||
for (const [f, val] of Object.entries(curve)) {
|
||||
fCurve[i].x.push(Number(f));
|
||||
fCurve[i].y.push(val.y[i]);
|
||||
}
|
||||
}
|
||||
return fCurve;
|
||||
}
|
||||
|
||||
buildFSplines(fCurve, type) {
|
||||
const fSplines = {};
|
||||
for (const i of Object.keys(fCurve)) {
|
||||
fSplines[i] = this.loadSpline(fCurve[i], type);
|
||||
}
|
||||
return fSplines;
|
||||
}
|
||||
|
||||
buildSingleFxyCurve(fSplines, cCurve, f, pointsCount) {
|
||||
const singleCurve = { [f]: { x: [], y: [] } };
|
||||
const keys = Object.keys(cCurve);
|
||||
const firstKey = keys[0];
|
||||
|
||||
for (let i = 0; i < pointsCount; i++) {
|
||||
singleCurve[f].x.push(cCurve[firstKey].x[i]);
|
||||
singleCurve[f].y.push(fSplines[i].interpolate(f));
|
||||
}
|
||||
|
||||
return singleCurve;
|
||||
}
|
||||
|
||||
buildXySplines(curves, type) {
|
||||
const xySplines = {};
|
||||
for (const f of Object.keys(curves)) {
|
||||
xySplines[f] = this.loadSpline(curves[f], type);
|
||||
}
|
||||
return xySplines;
|
||||
}
|
||||
|
||||
loadSpline(curve, type) {
|
||||
const splineObj = new Interpolation();
|
||||
splineObj.load_spline(curve.x, curve.y, type);
|
||||
return splineObj;
|
||||
}
|
||||
|
||||
calculateFxyXRange(value) {
|
||||
|
||||
const keys = Object.keys(this.inputCurve).map(Number).sort((a, b) => a - b);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const cur = keys[i];
|
||||
const next = keys[i + 1];
|
||||
|
||||
if (value === cur) {
|
||||
this.currentFxyXMin = this.xValues[cur].min;
|
||||
this.currentFxyXMax = this.xValues[cur].max;
|
||||
return;
|
||||
}
|
||||
|
||||
if (next && value > cur && value < next) {
|
||||
this.currentFxyXMin = this.interpolation.interpolate_lin_single_point(
|
||||
value, cur, next, this.xValues[cur].min, this.xValues[next].min
|
||||
);
|
||||
this.currentFxyXMax = this.interpolation.interpolate_lin_single_point(
|
||||
value, cur, next, this.xValues[cur].max, this.xValues[next].max
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getOutput() {
|
||||
return {
|
||||
x: this.currentX,
|
||||
y: this.yOutput,
|
||||
f: this.currentF,
|
||||
yOutputPosVsPeak: {
|
||||
peakOnlyPercentage: this.calcRelativePositionToPeak(this.currentFxyCurve[this.fDimension], this.outputY).peakOnlyPercentage,
|
||||
rangeBasedPercentage: this.calcRelativePositionToPeak(this.currentFxyCurve[this.fDimension], this.outputY).rangeBasedPercentage
|
||||
},
|
||||
posXyPeak: this.getPosXofYpeak(this.currentFxyCurve[this.fDimension]),
|
||||
xRange: { min: this.currentFxyXMin, max: this.currentFxyXMax },
|
||||
yRange: { min: this.currentFxyYMin, max: this.currentFxyYMax },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Predict;
|
||||
|
||||
/*
|
||||
// Example usage
|
||||
let example =
|
||||
{
|
||||
0:
|
||||
{
|
||||
x:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
y:[5, 15, 25, 35, 45, 55, 45, 35, 25, 15],
|
||||
},
|
||||
100:
|
||||
{
|
||||
x:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
y:[50, 150, 250, 350, 450, 550, 450, 350, 250, 150],
|
||||
}
|
||||
}
|
||||
|
||||
//set curve data in config
|
||||
let config = {curve:example};
|
||||
|
||||
var predict = new Predict(config=config);
|
||||
|
||||
console.log(" showing curve data");
|
||||
console.log(predict.inputCurveData);
|
||||
|
||||
console.log(" showing config data");
|
||||
console.log(predict.config);
|
||||
|
||||
// specify dimension f if there is no dim f then specify 0 as example 2
|
||||
console.log(" showing config data");
|
||||
console.log(predict.config);
|
||||
|
||||
console.log(`lowest y value ever seen : ${predict.yValues.lowest}`);
|
||||
console.log(`higehst y value ever seen : ${predict.yValues.highest}`);
|
||||
|
||||
predict.fDimension = 0;
|
||||
|
||||
console.log(`default x : ${predict.currentX}`);
|
||||
console.log(`min x : ${predict.currentFxyXMin} , max x : ${predict.currentFxyXMax} for f : ${predict.fDimension}`);
|
||||
console.log(`min y : ${predict.currentFxyYMin} , max y : ${predict.currentFxyYMax} for f : ${predict.fDimension}`);
|
||||
console.log(`Y prediction is= ${predict.outputY} @ f : ${predict.fDimension} `);
|
||||
|
||||
// specify x value to predict y
|
||||
const yVal = predict.y(x=0);
|
||||
console.log(`For x : ${predict.currentX} is the predicted value ${yVal} @ f : ${predict.fDimension} `);
|
||||
console.log(predict.retrieveActiveCurve());
|
||||
const peak = predict.getLocalPeak(predict.currentFxyCurve[predict.fDimension].y);
|
||||
|
||||
console.log(predict.getPosXofYpeak(predict.currentFxyCurve[predict.fDimension]));
|
||||
|
||||
const { peakOnlyPercentage, rangeBasedPercentage } = predict.calcRelativePositionToPeak(predict.currentFxyCurve[predict.fDimension], predict.outputY);
|
||||
console.log(`Peak-only percentage: ${peakOnlyPercentage}%, Range-based percentage: ${rangeBasedPercentage}%`);
|
||||
|
||||
|
||||
//*/
|
||||
Reference in New Issue
Block a user