kld-polynomial
Version:
A class of simple polynomial functionality including root finding
1,163 lines (974 loc) • 32.3 kB
JavaScript
/* eslint-disable camelcase */
/**
* Polynomial.js
*
* @module Polynomial
* @copyright 2002-2019 Kevin Lindsey<br>
* -<br>
* Contribution {@link http://github.com/Quazistax/kld-polynomial}<br>
* copyright 2015 Robert Benko (Quazistax) <quazistax@gmail.com><br>
* MIT license
*/
/**
* Sign of a number (+1, -1, +0, -0).
*
* @param {number} x
* @returns {number}
*/
function sign(x) {
// eslint-disable-next-line no-self-compare
return typeof x === "number" ? x ? x < 0 ? -1 : 1 : x === x ? x : NaN : NaN;
}
/**
* Polynomial
*
* @memberof module:kld-polynomial
*/
class Polynomial {
/**
* Polynomial
*
* @param {Array<number>} coefs
* @returns {module:kld-polynomial.Polynomial}
*/
constructor(...coefs) {
this.coefs = [];
for (let i = coefs.length - 1; i >= 0; i--) {
this.coefs.push(coefs[i]);
}
this._variable = "t";
this._s = 0;
}
/**
* Based on polint in "Numerical Recipes in C, 2nd Edition", pages 109-110
*
* @param {Array<number>} xs
* @param {Array<number>} ys
* @param {number} n
* @param {number} offset
* @param {number} x
*
* @returns {{y: number, dy: number}}
*/
static interpolate(xs, ys, n, offset, x) {
if (xs.constructor !== Array || ys.constructor !== Array) {
throw new TypeError("xs and ys must be arrays");
}
if (isNaN(n) || isNaN(offset) || isNaN(x)) {
throw new TypeError("n, offset, and x must be numbers");
}
let i, y;
let dy = 0;
const c = new Array(n);
const d = new Array(n);
let ns = 0;
let diff = Math.abs(x - xs[offset]);
for (i = 0; i < n; i++) {
const dift = Math.abs(x - xs[offset + i]);
if (dift < diff) {
ns = i;
diff = dift;
}
c[i] = d[i] = ys[offset + i];
}
y = ys[offset + ns];
ns--;
for (let m = 1; m < n; m++) {
for (i = 0; i < n - m; i++) {
const ho = xs[offset + i] - x;
const hp = xs[offset + i + m] - x;
const w = c[i + 1] - d[i];
let den = ho - hp;
if (den === 0.0) {
throw new RangeError("Unable to interpolate polynomial. Two numbers in n were identical (to within roundoff)");
}
den = w / den;
d[i] = hp * den;
c[i] = ho * den;
}
dy = (2 * (ns + 1) < (n - m)) ? c[ns + 1] : d[ns--];
y += dy;
}
return {y, dy};
}
/**
* Newton's (Newton-Raphson) method for finding Real roots on univariate function. <br/>
* When using bounds, algorithm falls back to secant if newton goes out of range.
* Bisection is fallback for secant when determined secant is not efficient enough.
* @see {@link http://en.wikipedia.org/wiki/Newton%27s_method}
* @see {@link http://en.wikipedia.org/wiki/Secant_method}
* @see {@link http://en.wikipedia.org/wiki/Bisection_method}
*
* @param {number} x0 - Initial root guess
* @param {Function} f - Function which root we are trying to find
* @param {Function} df - Derivative of function f
* @param {number} max_iterations - Maximum number of algorithm iterations
* @param {number} [min] - Left bound value
* @param {number} [max] - Right bound value
* @returns {number} root
*/
static newtonSecantBisection(x0, f, df, max_iterations, min, max) {
let x, prev_dfx = 0, dfx, prev_x_ef_correction = 0, x_correction, x_new;
let y, y_atmin, y_atmax;
x = x0;
const ACCURACY = 14;
const min_correction_factor = Math.pow(10, -ACCURACY);
const isBounded = (typeof min === "number" && typeof max === "number");
if (isBounded) {
if (min > max) {
throw new RangeError("Min must be greater than max");
}
y_atmin = f(min);
y_atmax = f(max);
if (sign(y_atmin) === sign(y_atmax)) {
throw new RangeError("Y values of bounds must be of opposite sign");
}
}
const isEnoughCorrection = function() {
// stop if correction is too small or if correction is in simple loop
return (Math.abs(x_correction) <= min_correction_factor * Math.abs(x)) ||
(prev_x_ef_correction === (x - x_correction) - x);
};
for (let i = 0; i < max_iterations; i++) {
dfx = df(x);
if (dfx === 0) {
if (prev_dfx === 0) {
// error
throw new RangeError("df(x) is zero");
}
else {
// use previous derivation value
dfx = prev_dfx;
}
// or move x a little?
// dfx = df(x != 0 ? x + x * 1e-15 : 1e-15);
}
prev_dfx = dfx;
y = f(x);
x_correction = y / dfx;
x_new = x - x_correction;
if (isEnoughCorrection()) {
break;
}
if (isBounded) {
if (sign(y) === sign(y_atmax)) {
max = x;
y_atmax = y;
}
else if (sign(y) === sign(y_atmin)) {
min = x;
y_atmin = y;
}
else {
x = x_new;
break;
}
if ((x_new < min) || (x_new > max)) {
if (sign(y_atmin) === sign(y_atmax)) {
break;
}
const RATIO_LIMIT = 50;
const AIMED_BISECT_OFFSET = 0.25; // [0, 0.5)
const dy = y_atmax - y_atmin;
const dx = max - min;
if (dy === 0) {
x_correction = x - (min + dx * 0.5);
}
else if (Math.abs(dy / Math.min(y_atmin, y_atmax)) > RATIO_LIMIT) {
x_correction = x - (min + dx * (0.5 + (Math.abs(y_atmin) < Math.abs(y_atmax) ? -AIMED_BISECT_OFFSET : AIMED_BISECT_OFFSET)));
}
else {
x_correction = x - (min - y_atmin / dy * dx);
}
x_new = x - x_correction;
if (isEnoughCorrection()) {
break;
}
}
}
prev_x_ef_correction = x - x_new;
x = x_new;
}
return x;
}
/**
* Clones this polynomial and return the clone.
*
* @returns {module:kld-polynomial.Polynomial}
*/
clone() {
const poly = new Polynomial();
poly.coefs = this.coefs.slice();
return poly;
}
/**
* eval
*
* @param {number} x
*/
eval(x) {
if (isNaN(x)) {
throw new TypeError(`Parameter must be a number. Found '${x}'`);
}
let result = 0;
for (let i = this.coefs.length - 1; i >= 0; i--) {
result = result * x + this.coefs[i];
}
return result;
}
/**
* add
*
* @param {module:kld-polynomial.Polynomial} that
* @returns {module:kld-polynomial.Polynomial}
*/
add(that) {
const result = new Polynomial();
const d1 = this.getDegree();
const d2 = that.getDegree();
const dmax = Math.max(d1, d2);
for (let i = 0; i <= dmax; i++) {
const v1 = (i <= d1) ? this.coefs[i] : 0;
const v2 = (i <= d2) ? that.coefs[i] : 0;
result.coefs[i] = v1 + v2;
}
return result;
}
/**
* multiply
*
* @param {module:kld-polynomial.Polynomial} that
* @returns {module:kld-polynomial.Polynomial}
*/
multiply(that) {
const result = new Polynomial();
for (let i = 0; i <= this.getDegree() + that.getDegree(); i++) {
result.coefs.push(0);
}
for (let i = 0; i <= this.getDegree(); i++) {
for (let j = 0; j <= that.getDegree(); j++) {
result.coefs[i + j] += this.coefs[i] * that.coefs[j];
}
}
return result;
}
/**
* divideEqualsScalar
*
* @deprecated To be replaced by divideScalar
* @param {number} scalar
*/
divideEqualsScalar(scalar) {
for (let i = 0; i < this.coefs.length; i++) {
this.coefs[i] /= scalar;
}
}
/**
* simplifyEquals
*
* @deprecated To be replaced by simplify
* @param {number} TOLERANCE
*/
simplifyEquals(TOLERANCE = 1e-12) {
for (let i = this.getDegree(); i >= 0; i--) {
if (Math.abs(this.coefs[i]) <= TOLERANCE) {
this.coefs.pop();
}
else {
break;
}
}
}
/**
* Sets small coefficients to zero.
*
* @deprecated To be replaced by removeZeros
* @param {number} TOLERANCE
* @returns {module:kld-polynomial.Polynomial}
*/
removeZerosEquals(TOLERANCE = 1e-15) {
const c = this.coefs;
const err = 10 * TOLERANCE * Math.abs(
c.reduce((pv, cv) => {
return Math.abs(cv) > Math.abs(pv) ? cv : pv;
})
);
for (let i = 0; i < c.length - 1; i++) {
if (Math.abs(c[i]) < err) {
c[i] = 0;
}
}
return this;
}
/**
* Scales polynomial so that leading coefficient becomes 1.
*
* @deprecated To be replaced by getMonic
* @returns {module:kld-polynomial.Polynomial}
*/
monicEquals() {
const c = this.coefs;
if (c[c.length - 1] !== 1) {
this.divideEqualsScalar(c[c.length - 1]);
}
return this;
}
/**
* toString
*
* @returns {string}
*/
toString() {
const coefs = [];
const signs = [];
for (let i = this.coefs.length - 1; i >= 0; i--) {
let value = Math.round(this.coefs[i] * 1000) / 1000;
if (value !== 0) {
const signString = (value < 0) ? " - " : " + ";
value = Math.abs(value);
if (i > 0) {
if (value === 1) {
value = this._variable;
}
else {
value += this._variable;
}
}
if (i > 1) {
value += "^" + i;
}
signs.push(signString);
coefs.push(value);
}
}
signs[0] = (signs[0] === " + ") ? "" : "-";
let result = "";
for (let i = 0; i < coefs.length; i++) {
result += signs[i] + coefs[i];
}
return result;
}
/**
* bisection
*
* @param {number} min
* @param {number} max
* @param {number} [TOLERANCE]
* @param {number} [ACCURACY]
* @returns {number}
*/
bisection(min, max, TOLERANCE = 1e-6, ACCURACY = 15) {
let minValue = this.eval(min);
let maxValue = this.eval(max);
let result;
if (Math.abs(minValue) <= TOLERANCE) {
result = min;
}
else if (Math.abs(maxValue) <= TOLERANCE) {
result = max;
}
else if (minValue * maxValue <= 0) {
const tmp1 = Math.log(max - min);
const tmp2 = Math.LN10 * ACCURACY;
const maxIterations = Math.ceil((tmp1 + tmp2) / Math.LN2);
for (let i = 0; i < maxIterations; i++) {
result = 0.5 * (min + max);
const value = this.eval(result);
if (Math.abs(value) <= TOLERANCE) {
break;
}
if (value * minValue < 0) {
max = result;
maxValue = value;
}
else {
min = result;
minValue = value;
}
}
}
return result;
}
/**
* Based on trapzd in "Numerical Recipes in C, 2nd Edition", page 137
*
* @param {number} min
* @param {number} max
* @param {number} n
* @returns {number}
*/
trapezoid(min, max, n) {
if (isNaN(min) || isNaN(max) || isNaN(n)) {
throw new TypeError("Parameters must be numbers");
}
const range = max - min;
if (n === 1) {
const minValue = this.eval(min);
const maxValue = this.eval(max);
this._s = 0.5 * range * (minValue + maxValue);
}
else {
const iter = 1 << (n - 2);
const delta = range / iter;
let x = min + 0.5 * delta;
let sum = 0;
for (let i = 0; i < iter; i++) {
sum += this.eval(x);
x += delta;
}
this._s = 0.5 * (this._s + range * sum / iter);
}
if (isNaN(this._s)) {
throw new TypeError("this._s is NaN");
}
return this._s;
}
/**
* Based on trapzd in "Numerical Recipes in C, 2nd Edition", page 139
*
* @param {number} min
* @param {number} max
* @returns {number}
*/
simpson(min, max) {
if (isNaN(min) || isNaN(max)) {
throw new TypeError("Parameters must be numbers");
}
const range = max - min;
let st = 0.5 * range * (this.eval(min) + this.eval(max));
let t = st;
let s = 4.0 * st / 3.0;
let os = s;
let ost = st;
const TOLERANCE = 1e-7;
let iter = 1;
for (let n = 2; n <= 20; n++) {
const delta = range / iter;
let x = min + 0.5 * delta;
let sum = 0;
for (let i = 1; i <= iter; i++) {
sum += this.eval(x);
x += delta;
}
t = 0.5 * (t + range * sum / iter);
st = t;
s = (4.0 * st - ost) / 3.0;
if (Math.abs(s - os) < TOLERANCE * Math.abs(os)) {
break;
}
os = s;
ost = st;
iter <<= 1;
}
return s;
}
/**
* romberg
*
* @param {number} min
* @param {number} max
* @returns {number}
*/
romberg(min, max) {
if (isNaN(min) || isNaN(max)) {
throw new TypeError("Parameters must be numbers");
}
const MAX = 20;
const K = 3;
const TOLERANCE = 1e-6;
const s = new Array(MAX + 1);
const h = new Array(MAX + 1);
let result = {y: 0, dy: 0};
h[0] = 1.0;
for (let j = 1; j <= MAX; j++) {
s[j - 1] = this.trapezoid(min, max, j);
if (j >= K) {
result = Polynomial.interpolate(h, s, K, j - K, 0.0);
if (Math.abs(result.dy) <= TOLERANCE * result.y) {
break;
}
}
s[j] = s[j - 1];
h[j] = 0.25 * h[j - 1];
}
return result.y;
}
/**
* Estimate what is the maximum polynomial evaluation error value under which polynomial evaluation could be in fact 0.
*
* @param {number} maxAbsX
* @returns {number}
*/
zeroErrorEstimate(maxAbsX) {
const poly = this;
const ERRF = 1e-15;
if (typeof maxAbsX === "undefined") {
const rb = poly.bounds();
maxAbsX = Math.max(Math.abs(rb.minX), Math.abs(rb.maxX));
}
if (maxAbsX < 0.001) {
return 2 * Math.abs(poly.eval(ERRF));
}
const n = poly.coefs.length - 1;
const an = poly.coefs[n];
return 10 * ERRF * poly.coefs.reduce((m, v, i) => {
const nm = v / an * Math.pow(maxAbsX, i);
return nm > m ? nm : m;
}, 0);
}
/**
* Calculates upper Real roots bounds. <br/>
* Real roots are in interval [negX, posX]. Determined by Fujiwara method.
* @see {@link http://en.wikipedia.org/wiki/Properties_of_polynomial_roots}
*
* @returns {{ negX: number, posX: number }}
*/
boundsUpperRealFujiwara() {
let a = this.coefs;
const n = a.length - 1;
const an = a[n];
if (an !== 1) {
a = this.coefs.map(v => v / an);
}
const b = a.map((v, i) => {
return (i < n)
? Math.pow(Math.abs((i === 0) ? v / 2 : v), 1 / (n - i))
: v;
});
let coefSelectionFunc;
const find2Max = function(acc, bi, i) {
if (coefSelectionFunc(i)) {
if (acc.max < bi) {
acc.nearmax = acc.max;
acc.max = bi;
}
else if (acc.nearmax < bi) {
acc.nearmax = bi;
}
}
return acc;
};
coefSelectionFunc = function(i) {
return i < n && a[i] < 0;
};
// eslint-disable-next-line unicorn/no-fn-reference-in-iterator
const max_nearmax_pos = b.reduce(find2Max, {max: 0, nearmax: 0});
coefSelectionFunc = function(i) {
return i < n && ((n % 2 === i % 2) ? a[i] < 0 : a[i] > 0);
};
// eslint-disable-next-line unicorn/no-fn-reference-in-iterator
const max_nearmax_neg = b.reduce(find2Max, {max: 0, nearmax: 0});
return {
negX: -2 * max_nearmax_neg.max,
posX: 2 * max_nearmax_pos.max
};
}
/**
* Calculates lower Real roots bounds. <br/>
* There are no Real roots in interval <negX, posX>. Determined by Fujiwara method.
* @see {@link http://en.wikipedia.org/wiki/Properties_of_polynomial_roots}
*
* @returns {{ negX: number, posX: number }}
*/
boundsLowerRealFujiwara() {
const poly = new Polynomial();
poly.coefs = this.coefs.slice().reverse();
const res = poly.boundsUpperRealFujiwara();
res.negX = 1 / res.negX;
res.posX = 1 / res.posX;
return res;
}
/**
* Calculates left and right Real roots bounds. <br/>
* Real roots are in interval [minX, maxX]. Combines Fujiwara lower and upper bounds to get minimal interval.
* @see {@link http://en.wikipedia.org/wiki/Properties_of_polynomial_roots}
*
* @returns {{ minX: number, maxX: number }}
*/
bounds() {
const urb = this.boundsUpperRealFujiwara();
const rb = {minX: urb.negX, maxX: urb.posX};
if (urb.negX === 0 && urb.posX === 0) {
return rb;
}
if (urb.negX === 0) {
rb.minX = this.boundsLowerRealFujiwara().posX;
}
else if (urb.posX === 0) {
rb.maxX = this.boundsLowerRealFujiwara().negX;
}
if (rb.minX > rb.maxX) {
rb.minX = rb.maxX = 0;
}
return rb;
// TODO: if sure that there are no complex roots
// (maybe by using Sturm's theorem) use:
// return this.boundsRealLaguerre();
}
/**
* Calculates absolute upper roots bound. <br/>
* All (Complex and Real) roots magnitudes are <= result. Determined by Rouche method.
* @see {@link http://en.wikipedia.org/wiki/Properties_of_polynomial_roots}
*
* @returns {number}
*/
boundUpperAbsRouche() {
const a = this.coefs;
const n = a.length - 1;
const max = a.reduce((prev, curr, i) => {
if (i !== n) {
curr = Math.abs(curr);
return (prev < curr) ? curr : prev;
}
return prev;
}, 0);
return 1 + max / Math.abs(a[n]);
}
/**
* Calculates absolute lower roots bound. <br/>
* All (Complex and Real) roots magnitudes are >= result. Determined by Rouche method.
* @see {@link http://en.wikipedia.org/wiki/Properties_of_polynomial_roots}
*
* @returns {number}
*/
boundLowerAbsRouche() {
const a = this.coefs;
const max = a.reduce((prev, curr, i) => {
if (i !== 0) {
curr = Math.abs(curr);
return (prev < curr) ? curr : prev;
}
return prev;
}, 0);
return Math.abs(a[0]) / (Math.abs(a[0]) + max);
}
/**
* Calculates left and right Real roots bounds.<br/>
* WORKS ONLY if all polynomial roots are Real.
* Real roots are in interval [minX, maxX]. Determined by Laguerre method.
* @see {@link http://en.wikipedia.org/wiki/Properties_of_polynomial_roots}
*
* @returns {{ minX: number, maxX: number }}
*/
boundsRealLaguerre() {
const a = this.coefs;
const n = a.length - 1;
const p1 = -a[n - 1] / (n * a[n]);
const undersqrt = a[n - 1] * a[n - 1] - 2 * n / (n - 1) * a[n] * a[n - 2];
let p2 = (n - 1) / (n * a[n]) * Math.sqrt(undersqrt);
if (p2 < 0) {
p2 = -p2;
}
return {
minX: p1 - p2,
maxX: p1 + p2
};
}
/**
* Root count by Descartes rule of signs. <br/>
* Returns maximum number of positive and negative real roots and minimum number of complex roots.
* @see {@link http://en.wikipedia.org/wiki/Descartes%27_rule_of_signs}
*
* @returns {{maxRealPos: number, maxRealNeg: number, minComplex: number}}
*/
countRootsDescartes() {
const a = this.coefs;
const n = a.length - 1;
const accum = a.reduce((acc, ai, i) => {
if (acc.prev_a !== 0 && ai !== 0) {
if ((acc.prev_a < 0) === (ai > 0)) {
acc.pos++;
}
if (((i % 2 === 0) !== (acc.prev_a < 0)) === ((i % 2 === 1) !== (ai > 0))) {
acc.neg++;
}
}
acc.prev_a = ai;
return acc;
}, {pos: 0, neg: 0, prev_a: 0});
return {
maxRealPos: accum.pos,
maxRealNeg: accum.neg,
minComplex: n - (accum.pos + accum.neg)
};
}
// getters and setters
/**
* get degree
*
* @returns {number}
*/
getDegree() {
return this.coefs.length - 1;
}
/**
* getDerivative
*
* @returns {module:kld-polynomial.Polynomial}
*/
getDerivative() {
const derivative = new Polynomial();
for (let i = 1; i < this.coefs.length; i++) {
derivative.coefs.push(i * this.coefs[i]);
}
return derivative;
}
/**
* getRoots
*
* @returns {Array<number>}
*/
getRoots() {
let result;
this.simplifyEquals();
switch (this.getDegree()) {
case 0: result = []; break;
case 1: result = this.getLinearRoot(); break;
case 2: result = this.getQuadraticRoots(); break;
case 3: result = this.getCubicRoots(); break;
case 4: result = this.getQuarticRoots(); break;
default:
result = [];
}
return result;
}
/**
* getRootsInInterval
*
* @param {number} min
* @param {number} max
* @returns {Array<number>}
*/
getRootsInInterval(min, max) {
const roots = [];
/**
* @param {number} value
*/
function push(value) {
if (typeof value === "number") {
roots.push(value);
}
}
if (this.getDegree() === 0) {
throw new RangeError("Unexpected empty polynomial");
}
else if (this.getDegree() === 1) {
push(this.bisection(min, max));
}
else {
// get roots of derivative
const deriv = this.getDerivative();
const droots = deriv.getRootsInInterval(min, max);
if (droots.length > 0) {
// find root on [min, droots[0]]
push(this.bisection(min, droots[0]));
// find root on [droots[i],droots[i+1]] for 0 <= i <= count-2
for (let i = 0; i <= droots.length - 2; i++) {
push(this.bisection(droots[i], droots[i + 1]));
}
// find root on [droots[count-1],xmax]
push(this.bisection(droots[droots.length - 1], max));
}
else {
// polynomial is monotone on [min,max], has at most one root
push(this.bisection(min, max));
}
}
return roots;
}
/**
* getLinearRoot
*
* @returns {number}
*/
getLinearRoot() {
const result = [];
const a = this.coefs[1];
if (a !== 0) {
result.push(-this.coefs[0] / a);
}
return result;
}
/**
* getQuadraticRoots
*
* @returns {Array<number>}
*/
getQuadraticRoots() {
const results = [];
if (this.getDegree() === 2) {
const a = this.coefs[2];
const b = this.coefs[1] / a;
const c = this.coefs[0] / a;
const d = b * b - 4 * c;
if (d > 0) {
const e = Math.sqrt(d);
results.push(0.5 * (-b + e));
results.push(0.5 * (-b - e));
}
else if (d === 0) {
// really two roots with same value, but we only return one
results.push(0.5 * -b);
}
// else imaginary results
}
return results;
}
/**
* getCubicRoots
*
* This code is based on MgcPolynomial.cpp written by David Eberly. His
* code along with many other excellent examples are avaiable at his site:
* http://www.geometrictools.com
*
* @returns {Array<number>}
*/
getCubicRoots() {
const results = [];
if (this.getDegree() === 3) {
const c3 = this.coefs[3];
const c2 = this.coefs[2] / c3;
const c1 = this.coefs[1] / c3;
const c0 = this.coefs[0] / c3;
const a = (3 * c1 - c2 * c2) / 3;
const b = (2 * c2 * c2 * c2 - 9 * c1 * c2 + 27 * c0) / 27;
const offset = c2 / 3;
let discrim = b * b / 4 + a * a * a / 27;
const halfB = b / 2;
const ZEROepsilon = this.zeroErrorEstimate();
if (Math.abs(discrim) <= ZEROepsilon) {
discrim = 0;
}
if (discrim > 0) {
const e = Math.sqrt(discrim);
let root; // eslint-disable-line no-shadow
let tmp = -halfB + e;
if (tmp >= 0) {
root = Math.pow(tmp, 1 / 3);
}
else {
root = -Math.pow(-tmp, 1 / 3);
}
tmp = -halfB - e;
if (tmp >= 0) {
root += Math.pow(tmp, 1 / 3);
}
else {
root -= Math.pow(-tmp, 1 / 3);
}
results.push(root - offset);
}
else if (discrim < 0) {
const distance = Math.sqrt(-a / 3);
const angle = Math.atan2(Math.sqrt(-discrim), -halfB) / 3;
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const sqrt3 = Math.sqrt(3);
results.push(2 * distance * cos - offset);
results.push(-distance * (cos + sqrt3 * sin) - offset);
results.push(-distance * (cos - sqrt3 * sin) - offset);
}
else {
let tmp;
if (halfB >= 0) {
tmp = -Math.pow(halfB, 1 / 3);
}
else {
tmp = Math.pow(-halfB, 1 / 3);
}
results.push(2 * tmp - offset);
// really should return next root twice, but we return only one
results.push(-tmp - offset);
}
}
return results;
}
/**
* Calculates roots of quartic polynomial. <br/>
* First, derivative roots are found, then used to split quartic polynomial
* into segments, each containing one root of quartic polynomial.
* Segments are then passed to newton's method to find roots.
*
* @returns {Array<number>} roots
*/
getQuarticRoots() {
let results = [];
const n = this.getDegree();
if (n === 4) {
const poly = new Polynomial();
poly.coefs = this.coefs.slice();
poly.divideEqualsScalar(poly.coefs[n]);
const ERRF = 1e-15;
if (Math.abs(poly.coefs[0]) < 10 * ERRF * Math.abs(poly.coefs[3])) {
poly.coefs[0] = 0;
}
const poly_d = poly.getDerivative();
const derrt = poly_d.getRoots().sort((a, b) => a - b);
const dery = [];
const nr = derrt.length - 1;
const rb = this.bounds();
const maxabsX = Math.max(Math.abs(rb.minX), Math.abs(rb.maxX));
const ZEROepsilon = this.zeroErrorEstimate(maxabsX);
for (let i = 0; i <= nr; i++) {
dery.push(poly.eval(derrt[i]));
}
for (let i = 0; i <= nr; i++) {
if (Math.abs(dery[i]) < ZEROepsilon) {
dery[i] = 0;
}
}
let i = 0;
const dx = Math.max(0.1 * (rb.maxX - rb.minX) / n, ERRF);
const guesses = [];
const minmax = [];
if (nr > -1) {
if (dery[0] !== 0) {
if (sign(dery[0]) !== sign(poly.eval(derrt[0] - dx) - dery[0])) {
guesses.push(derrt[0] - dx);
minmax.push([rb.minX, derrt[0]]);
}
}
else {
results.push(derrt[0], derrt[0]);
i++;
}
for (; i < nr; i++) {
if (dery[i + 1] === 0) {
results.push(derrt[i + 1], derrt[i + 1]);
i++;
}
else if (sign(dery[i]) !== sign(dery[i + 1])) {
guesses.push((derrt[i] + derrt[i + 1]) / 2);
minmax.push([derrt[i], derrt[i + 1]]);
}
}
if (dery[nr] !== 0 && sign(dery[nr]) !== sign(poly.eval(derrt[nr] + dx) - dery[nr])) {
guesses.push(derrt[nr] + dx);
minmax.push([derrt[nr], rb.maxX]);
}
}
/**
* @param {number} x
* @returns {number}
*/
const f = function(x) {
return poly.eval(x);
};
/**
* @param {number} x
* @returns {number}
*/
const df = function(x) {
return poly_d.eval(x);
};
if (guesses.length > 0) {
for (i = 0; i < guesses.length; i++) {
guesses[i] = Polynomial.newtonSecantBisection(guesses[i], f, df, 32, minmax[i][0], minmax[i][1]);
}
}
results = results.concat(guesses);
}
return results;
}
}
export default Polynomial;