nerdamer
Version:
javascript light-weight symbolic math expression evaluator
1,312 lines (1,239 loc) • 198 kB
JavaScript
/*
* Author : Martin Donk
* Website : http://www.nerdamer.com
* Email : martin.r.donk@gmail.com
* License : MIT
* Source : https://github.com/jiggzson/nerdamer
*/
/* global module, Function */
if((typeof module) !== 'undefined') {
var nerdamer = require('./nerdamer.core.js');
require('./Calculus.js');
}
(function () {
"use strict";
/*shortcuts*/
var core = nerdamer.getCore(),
_ = core.PARSER,
N = core.groups.N,
P = core.groups.P,
S = core.groups.S,
EX = core.groups.EX,
FN = core.groups.FN,
PL = core.groups.PL,
CP = core.groups.CP,
CB = core.groups.CB,
keys = core.Utils.keys,
even = core.Utils.even,
variables = core.Utils.variables,
format = core.Utils.format,
round = core.Utils.round,
Frac = core.Frac,
isInt = core.Utils.isInt,
Symbol = core.Symbol,
CONST_HASH = core.Settings.CONST_HASH,
math = core.Utils.importFunctions(),
evaluate = core.Utils.evaluate;
//*************** CLASSES ***************//
/**
* Converts a symbol into an equivalent polynomial arrays of
* the form [[coefficient_1, power_1],[coefficient_2, power_2], ... ]
* Univariate polymials only.
* @param {Symbol|Number} symbol
* @param {String} variable The variable name of the polynomial
* @param {int} order
*/
function Polynomial(symbol, variable, order) {
if(core.Utils.isSymbol(symbol)) {
this.parse(symbol);
this.variable = this.variable || variable;
}
else if(!isNaN(symbol)) {
order = order || 0;
if(variable === undefined)
throw new core.exceptions.InvalidVariableNameError('Polynomial expects a variable name when creating using order');
this.coeffs = [];
this.coeffs[order] = symbol;
this.fill(symbol);
}
else if(typeof symbol === 'string') {
this.parse(_.parse(symbol));
}
}
/**
* Creates a Polynomial given an array of coefficients
* @param {int[]} arr
* @param {String} variable
* @returns {Polynomial}
*/
Polynomial.fromArray = function (arr, variable) {
if(typeof variable === 'undefined')
throw new core.exceptions.InvalidVariableNameError('A variable name must be specified when creating polynomial from array');
var p = new Polynomial();
p.coeffs = arr;
p.variable = variable;
return p;
};
Polynomial.fit = function (c1, c2, n, base, p, variable) {
//after having looped through and mod 10 the number to get the matching factor
var terms = new Array(p + 1),
t = n - c2;
terms[0] = c2; //the constants is assumed to be correct
//constant for x^p is also assumed know so add
terms[p] = c1;
t -= c1 * Math.pow(base, p);
//start fitting
for(var i = p - 1; i > 0; i--) {
var b = Math.pow(base, i), //we want as many wholes as possible
q = t / b,
sign = Math.sign(q);
var c = sign * Math.floor(Math.abs(q));
t -= c * b;
terms[i] = c;
}
if(t !== 0)
return null;
for(var i = 0; i < terms.length; i++)
terms[i] = new Frac(terms[i]);
return Polynomial.fromArray(terms, variable);
};
Polynomial.prototype = {
/**
* Converts Symbol to Polynomial
* @param {Symbol} symbol
* @param {Array} c - a collector array
* @returns {Polynomial}
*/
parse: function (symbol, c) {
this.variable = variables(symbol)[0];
if(!symbol.isPoly())
throw core.exceptions.NerdamerTypeError('Polynomial Expected! Received ' + core.Utils.text(symbol));
c = c || [];
if(!symbol.power.absEquals(1))
symbol = _.expand(symbol);
if(symbol.group === core.groups.N) {
c[0] = symbol.multiplier;
}
else if(symbol.group === core.groups.S) {
c[symbol.power.toDecimal()] = symbol.multiplier;
}
else {
for(var x in symbol.symbols) {
var sub = symbol.symbols[x],
p = sub.power;
if(core.Utils.isSymbol(p))
throw new core.exceptions.NerdamerTypeError('power cannot be a Symbol');
p = sub.group === N ? 0 : p.toDecimal();
if(sub.symbols) {
this.parse(sub, c);
}
else {
c[p] = sub.multiplier;
}
}
}
this.coeffs = c;
this.fill();
},
/**
* Fills in the holes in a polynomial with zeroes
* @param {Number} x - The number to fill the holes with
*/
fill: function (x) {
x = Number(x) || 0;
var l = this.coeffs.length;
for(var i = 0; i < l; i++) {
if(this.coeffs[i] === undefined) {
this.coeffs[i] = new Frac(x);
}
}
return this;
},
/**
* Removes higher order zeros or a specific coefficient
* @returns {Array}
*/
trim: function () {
var l = this.coeffs.length;
while(l--) {
var c = this.coeffs[l];
var equalsZero = c.equals(0);
if(c && equalsZero) {
if(l === 0)
break;
this.coeffs.pop();
}
else
break;
}
return this;
},
/*
* Returns polynomial mod p **currently fails**
* @param {Number} p
* @returns {Polynomial}
*/
modP: function (p) {
var l = this.coeffs.length;
for(var i = 0; i < l; i++) {
var c = this.coeffs[i];
if(c < 0) { //go borrow
var b; //a coefficient > 0
for(var j = i; j < l; j++) {//starting from where we left off
if(this.coeffs[j] > 0) {
b = this.coeffs[j];
break;
}
}
if(b) { //if such a coefficient exists
for(j; j > i; j--) { //go down the line and adjust using p
this.coeffs[j] = this.coeffs[j].subtract(new Frac(1));
this.coeffs[j - 1] = this.coeffs[j - 1].add(new Frac(p));
}
c = this.coeffs[i]; //reset c
}
}
var d = c.mod(p);
var w = c.subtract(d).divide(p);
if(!w.equals(0)) {
var up_one = i + 1;
var next = this.coeffs[up_one] || new Frac(0);
next = next.add(w);
this.coeffs[up_one] = new Frac(next);
this.coeffs[i] = new Frac(d);
}
}
return this;
},
/**
* Adds together 2 polynomials
* @param {Polynomial} poly
*/
add: function (poly) {
var l = Math.max(this.coeffs.length, poly.coeffs.length);
for(var i = 0; i < l; i++) {
var a = (this.coeffs[i] || new Frac(0)),
b = (poly.coeffs[i] || new Frac(0));
this.coeffs[i] = a.add(b);
}
return this;
},
/**
* Adds together 2 polynomials
* @param {Polynomial} poly
*/
subtract: function (poly) {
var l = Math.max(this.coeffs.length, poly.coeffs.length);
for(var i = 0; i < l; i++) {
var a = (this.coeffs[i] || new Frac(0)),
b = (poly.coeffs[i] || new Frac(0));
this.coeffs[i] = a.subtract(b);
}
return this;
},
divide: function (poly) {
var variable = this.variable,
dividend = core.Utils.arrayClone(this.coeffs),
divisor = core.Utils.arrayClone(poly.coeffs),
n = dividend.length,
mp = divisor.length - 1,
quotient = [];
//loop through the dividend
for(var i = 0; i < n; i++) {
var p = n - (i + 1);
//get the difference of the powers
var d = p - mp;
//get the quotient of the coefficients
var q = dividend[p].divide(divisor[mp]);
if(d < 0)
break;//the divisor is not greater than the dividend
//place it in the quotient
quotient[d] = q;
for(var j = 0; j <= mp; j++) {
//reduce the dividend
dividend[j + d] = dividend[j + d].subtract((divisor[j].multiply(q)));
}
}
//clean up
var p1 = Polynomial.fromArray(dividend, variable || 'x').trim(), //pass in x for safety
p2 = Polynomial.fromArray(quotient, variable || 'x');
return [p2, p1];
},
multiply: function (poly) {
var l1 = this.coeffs.length, l2 = poly.coeffs.length,
c = []; //array to be returned
for(var i = 0; i < l1; i++) {
var x1 = this.coeffs[i];
for(var j = 0; j < l2; j++) {
var k = i + j, //add the powers together
x2 = poly.coeffs[j],
e = c[k] || new Frac(0); //get the existing term from the new array
c[k] = e.add(x1.multiply(x2)); //multiply the coefficients and add to new polynomial array
}
}
this.coeffs = c;
return this;
},
/**
* Checks if a polynomial is zero
* @returns {Boolean}
*/
isZero: function () {
var l = this.coeffs.length;
for(var i = 0; i < l; i++) {
var e = this.coeffs[i];
if(!e.equals(0))
return false;
}
return true;
},
/**
* Substitutes in a number n into the polynomial p(n)
* @param {Number} n
* @returns {Frac}
*/
sub: function (n) {
var sum = new Frac(0), l = this.coeffs.length;
for(var i = 0; i < l; i++) {
var t = this.coeffs[i];
if(!t.equals(0))
sum = sum.add(t.multiply(new Frac(Math.pow(n, i))));
}
return sum;
},
/**
* Returns a clone of the polynomial
* @returns {Polynomial}
*/
clone: function () {
var p = new Polynomial();
p.coeffs = this.coeffs;
p.variable = this.variable;
return p;
},
/**
* Gets the degree of the polynomial
* @returns {Number}
*/
deg: function () {
this.trim();
return this.coeffs.length - 1;
},
/**
* Returns a lead coefficient
* @returns {Frac}
*/
lc: function () {
return this.coeffs[this.deg()].clone();
},
/**
* Converts polynomial into a monic polynomial
* @returns {Polynomial}
*/
monic: function () {
var lc = this.lc(), l = this.coeffs.length;
for(var i = 0; i < l; i++)
this.coeffs[i] = this.coeffs[i].divide(lc);
return this;
},
/**
* Returns the GCD of two polynomials
* @param {Polynomial} poly
* @returns {Polynomial}
*/
gcd: function (poly) {
//get the maximum power of each
var mp1 = this.coeffs.length - 1,
mp2 = poly.coeffs.length - 1,
T;
//swap so we always have the greater power first
if(mp1 < mp2) {
return poly.gcd(this);
}
var a = this;
while(!poly.isZero()) {
var t = poly.clone();
a = a.clone();
T = a.divide(t);
poly = T[1];
a = t;
}
var gcd = core.Math2.QGCD.apply(null, a.coeffs);
if(!gcd.equals(1)) {
var l = a.coeffs.length;
for(var i = 0; i < l; i++) {
a.coeffs[i] = a.coeffs[i].divide(gcd);
}
}
return a;
},
/**
* Differentiates the polynomial
* @returns {Polynomial}
*/
diff: function () {
var new_array = [], l = this.coeffs.length;
for(var i = 1; i < l; i++)
new_array.push(this.coeffs[i].multiply(new Frac(i)));
this.coeffs = new_array;
return this;
},
/**
* Integrates the polynomial
* @returns {Polynomial}
*/
integrate: function () {
var new_array = [0], l = this.coeffs.length;
for(var i = 0; i < l; i++) {
var c = new Frac(i + 1);
new_array[c] = this.coeffs[i].divide(c);
}
this.coeffs = new_array;
return this;
},
/**
* Returns the Greatest common factor of the polynomial
* @param {bool} toPolynomial - true if a polynomial is wanted
* @returns {Frac|Polynomial}
*/
gcf: function (toPolynomial) {
//get the first nozero coefficient and returns its power
var fnz = function (a) {
for(var i = 0; i < a.length; i++)
if(!a[i].equals(0))
return i;
},
ca = [];
for(var i = 0; i < this.coeffs.length; i++) {
var c = this.coeffs[i];
if(!c.equals(0) && ca.indexOf(c) === -1)
ca.push(c);
}
var p = [core.Math2.QGCD.apply(undefined, ca), fnz(this.coeffs)].toDecimal();
if(toPolynomial) {
var parr = [];
parr[p[1] - 1] = p[0];
p = Polynomial.fromArray(parr, this.variable).fill();
}
return p;
},
/**
* Raises a polynomial P to a power p -> P^p. e.g. (x+1)^2
* @param {bool} incl_img - Include imaginary numbers
*/
quad: function (incl_img) {
var roots = [];
if(this.coeffs.length > 3)
throw new Error('Cannot calculate quadratic order of ' + (this.coeffs.length - 1));
if(this.coeffs.length === 0)
throw new Error('Polynomial array has no terms');
var a = this.coeffs[2] || 0, b = this.coeffs[1] || 0, c = this.coeffs[0];
var dsc = b * b - 4 * a * c;
if(dsc < 0 && !incl_img)
return roots;
else {
roots[0] = (-b + Math.sqrt(dsc)) / (2 * a);
roots[1] = (-b - Math.sqrt(dsc)) / (2 * a);
}
return roots;
},
/**
* Makes polynomial square free
* @returns {Array}
*/
squareFree: function () {
var a = this.clone(),
i = 1,
b = a.clone().diff(),
c = a.clone().gcd(b),
w = a.divide(c)[0];
var output = Polynomial.fromArray([new Frac(1)], a.variable);
while(!c.equalsNumber(1)) {
var y = w.gcd(c);
var z = w.divide(y)[0];
//one of the factors may have shown up since it's square but smaller than the
//one where finding
if(!z.equalsNumber(1) && i > 1) {
var t = z.clone();
for(var j = 1; j < i; j++)
t.multiply(z.clone());
z = t;
}
output = output.multiply(z);
i++;
w = y;
c = c.divide(y)[0];
}
return [output, w, i];
},
/**
* Converts polynomial to Symbol
* @returns {Symbol}
*/
toSymbol: function () {
var l = this.coeffs.length,
variable = this.variable;
if(l === 0)
return new core.Symbol(0);
var end = l - 1, str = '';
for(var i = 0; i < l; i++) {
//place the plus sign for all but the last one
var plus = i === end ? '' : '+',
e = this.coeffs[i];
if(!e.equals(0))
str += (e + '*' + variable + '^' + i + plus);
}
return _.parse(str);
},
/**
* Checks if polynomial is equal to a number
* @param {Number} x
* @returns {Boolean}
*/
equalsNumber: function (x) {
this.trim();
return this.coeffs.length === 1 && this.coeffs[0].toDecimal() === String(x);
},
toString: function () {
return this.toSymbol().toString();
}
};
/**
* TODO
* ===================================================================================
* THIS METHOD HAS A NASTY HIDDEN BUG. IT HAS INCONSISTENT RETURN TYPES PRIMARILY DUE TO
* WRONG ASSUMPTIONS AT THE BEGINNING. THE ASSUMPTION WAS THAT COEFFS WERE ALWAYS GOING BE NUMBERS
* NOT TAKING INTO ACCOUNT THAT IMAGINARY NUMBERS. FIXING THIS BREAKS WAY TOO MANY TESTS
* AT THEM MOMENT WHICH I DON'T HAVE TO FIX
* ===================================================================================
* If the symbols is of group PL or CP it will return the multipliers of each symbol
* as these are polynomial coefficients. CB symbols are glued together by multiplication
* so the symbol multiplier carries the coefficients for all contained symbols.
* For S it just returns it's own multiplier. This function doesn't care if it's a polynomial or not
* @param {Array} c The coefficient array
* @param {boolean} with_order
* @return {Array}
*/
Symbol.prototype.coeffs = function (c, with_order) {
if(with_order && !this.isPoly(true))
_.error('Polynomial expected when requesting coefficients with order');
c = c || [];
var s = this.clone().distributeMultiplier();
if(s.isComposite()) {
for(var x in s.symbols) {
var sub = s.symbols[x];
if(sub.isComposite()) {
sub.clone().distributeMultiplier().coeffs(c, with_order);
}
else {
if(with_order)
c[sub.isConstant() ? 0 : sub.power.toDecimal()] = sub.multiplier;
else {
c.push(sub.multiplier);
}
}
}
}
else {
if(with_order)
c[s.isConstant(true) ? 0 : s.power.toDecimal()] = s.multiplier;
else {
if(s.group === CB && s.isImaginary()) {
var m = new Symbol(s.multiplier);
s.each(function (x) {
//add the imaginary part
if(x.isConstant(true) || x.imaginary)
m = _.multiply(m, x);
});
c.push(m);
}
else
c.push(s.multiplier);
}
}
//fill the holes
if(with_order) {
for(var i = 0; i < c.length; i++)
if(c[i] === undefined)
c[i] = new Symbol(0);
}
return c;
};
Symbol.prototype.tBase = function (map) {
if(typeof map === 'undefined')
throw new Error('Symbol.tBase requires a map object!');
var terms = [];
var symbols = this.collectSymbols(null, null, null, true),
l = symbols.length;
for(var i = 0; i < l; i++) {
var symbol = symbols[i],
g = symbol.group,
nterm = new MVTerm(symbol.multiplier, [], map);
if(g === CB) {
for(var x in symbol.symbols) {
var sym = symbol.symbols[x];
nterm.terms[map[x]] = sym.power;
}
}
else {
nterm.terms[map[symbol.value]] = symbol.power;
}
terms.push(nterm.fill());
nterm.updateCount();
}
return terms;
};
Symbol.prototype.altVar = function (x) {
var m = this.multiplier.toString(), p = this.power.toString();
return (m === '1' ? '' : m + '*') + x + (p === '1' ? '' : '^' + p);
};
/**
* Checks to see if the symbols contain the same variables
* @param {Symbol} symbol
* @returns {Boolean}
*/
Symbol.prototype.sameVars = function (symbol) {
if(!(this.symbols || this.group === symbol.group))
return false;
for(var x in this.symbols) {
var a = this.symbols[x], b = symbol.symbols[x];
if(!b)
return false;
if(a.value !== b.value)
return false;
}
return true;
};
/**
* Groups the terms in a symbol with respect to a variable
* For instance the symbol {a*b^2*x^2+a*b*x^2+x+6} returns [6,1,a*b+a*b^2]
* @returns {Factors}
*/
Symbol.prototype.groupTerms = function (x) {
x = String(x);
var f, p, egrouped;
var grouped = [];
this.each(function (e) {
if(e.group === PL) {
egrouped = e.groupTerms(x);
for(var i = 0; i < egrouped.length; i++) {
var el = egrouped[i];
if(el)
grouped[i] = el;
}
}
else {
f = core.Utils.decompose_fn(e, x, true);
p = f.x.value === x ? Number(f.x.power) : 0;
//check if there's an existing value
grouped[p] = _.add(grouped[p] || new Symbol(0), f.a);
}
});
return grouped;
};
/**
* Use this to collect Factors
* @returns {Symbol[]}
*/
Symbol.prototype.collectFactors = function () {
var factors = [];
if(this.group === CB)
this.each(function (x) {
factors.push(x.clone());
});
else
factors.push(this.clone());
return factors;
};
/**
* A container class for factors
* @returns {Factors}
*/
function Factors() {
this.factors = {};
this.length = 0;
}
;
Factors.prototype.getNumberSymbolics = function () {
var n = 0;
this.each(function (x) {
if(!x.isConstant(true))
n++;
});
return n;
};
/**
* Adds the factors to the factor object
* @param {Symbo} s
* @returns {Factors}
*/
Factors.prototype.add = function (s) {
if(s.equals(0))
return this; //nothing to add
//we don't want to carry -1 as a factor. If a factor already exists,
//then add the minus one to that factor and return.
if(s.equals(-1) && this.length > 0) {
var fo = core.Utils.firstObject(this.factors, null, true);
this.add(_.symfunction(core.Settings.PARENTHESIS, [fo.obj]).negate());
delete this.factors[fo.key];
this.length--;
return this;
}
if(s.group === CB) {
var factors = this;
if(!s.multiplier.equals(1))
factors.add(new Symbol(s.multiplier));
s.each(function (x) {
factors.add(x);
});
}
else {
if(this.preAdd) //if a preAdd function was defined call it to do prep
s = this.preAdd(s);
if(this.pFactor) //if the symbol isn't linear add back the power
s = _.pow(s, new Symbol(this.pFactor));
var is_constant = s.isConstant();
if(is_constant && s.equals(1))
return this; //don't add 1
var v = is_constant ? s.value : s.text();
if(v in this.factors) {
this.factors[v] = _.multiply(this.factors[v], s);
//did the addition cancel out the existing factor? If so remove it and decrement the length
if(this.factors[v].equals(1)) {
delete this.factors[v];
this.length--;
}
}
else {
this.factors[v] = s;
this.length++;
}
}
return this;
};
/**
* Converts the factor object to a Symbol
* @returns {Symbol}
*/
Factors.prototype.toSymbol = function () {
var factored = new Symbol(1);
var factors = Object.values(this.factors).sort(function (a, b) {
return a.group > b.group;
});
for(var i = 0, l = factors.length; i < l; i++) {
var f = factors[i];
//don't wrap group S or FN
var factor = f.power.equals(1) && f.fname !== '' /* don't wrap it twice */ ?
_.symfunction(core.PARENTHESIS, [f]) : f;
factored = _.multiply(factored, factor);
}
if(factored.fname === '')
factored = Symbol.unwrapPARENS(factored);
return factored;
};
/**
* Merges 2 factor objects into one
* @param {Factor} o
* @returns {Factors}
*/
Factors.prototype.merge = function (o) {
for(var x in o) {
if(x in this.factors)
this.factors[x] = _.multiply(this.factors[x], o[x]);
else
this.factors[x] = o[x];
}
return this;
};
/**
* The iterator for the factor object
* @param {Function} f - callback
* @returns {Factor}
*/
Factors.prototype.each = function (f) {
for(var x in this.factors) {
var factor = this.factors[x];
if(factor.fname === core.PARENTHESIS && factor.isLinear())
factor = factor.args[0];
f.call(this, factor, x);
}
return this;
};
/**
* Return the number of factors contained in the factor object
* @returns {int}
*/
Factors.prototype.count = function () {
return keys(this.factors).length;
};
/**
* Cleans up factors from -1
* @returns {undefined}
*/
Factors.prototype.clean = function () {
try {
var h = core.Settings.CONST_HASH;
if(this.factors[h].lessThan(0)) {
if(this.factors[h].equals(-1))
delete this.factors[h];
else
this.factors[h].negate();
this.each(function (x) {
x.negate();
});
}
}
catch(e) {
}
;
};
Factors.prototype.toString = function () {
return this.toSymbol().toString();
};
//a wrapper for performing multivariate division
function MVTerm(coeff, terms, map) {
this.terms = terms || [];
this.coeff = coeff;
this.map = map; //careful! all maps are the same object
this.sum = new core.Frac(0);
this.image = undefined;
}
;
MVTerm.prototype.updateCount = function () {
this.count = this.count || 0;
for(var i = 0; i < this.terms.length; i++) {
if(!this.terms[i].equals(0))
this.count++;
}
return this;
};
MVTerm.prototype.getVars = function () {
var vars = [];
for(var i = 0; i < this.terms.length; i++) {
var term = this.terms[i],
rev_map = this.getRevMap();
if(!term.equals(0))
vars.push(this.rev_map[i]);
}
return vars.join(' ');
};
MVTerm.prototype.len = function () {
if(typeof this.count === 'undefined') {
this.updateCount();
}
return this.count;
};
MVTerm.prototype.toSymbol = function (rev_map) {
rev_map = rev_map || this.getRevMap();
var symbol = new Symbol(this.coeff);
for(var i = 0; i < this.terms.length; i++) {
var v = rev_map[i],
t = this.terms[i];
if(t.equals(0) || v === CONST_HASH)
continue;
var mapped = new Symbol(v);
mapped.power = t;
symbol = _.multiply(symbol, mapped);
}
return symbol;
};
MVTerm.prototype.getRevMap = function () {
if(this.rev_map)
return this.rev_map;
var o = {};
for(var x in this.map)
o[this.map[x]] = x;
this.rev_map = o;
return o;
};
MVTerm.prototype.generateImage = function () {
this.image = this.terms.join(' ');
return this;
},
MVTerm.prototype.getImg = function () {
if(!this.image)
this.generateImage();
return this.image;
},
MVTerm.prototype.fill = function () {
var l = this.map.length;
for(var i = 0; i < l; i++) {
if(typeof this.terms[i] === 'undefined')
this.terms[i] = new core.Frac(0);
else {
this.sum = this.sum.add(this.terms[i]);
}
}
return this;
};
MVTerm.prototype.divide = function (mvterm) {
var c = this.coeff.divide(mvterm.coeff),
l = this.terms.length,
new_mvterm = new MVTerm(c, [], this.map);
for(var i = 0; i < l; i++) {
new_mvterm.terms[i] = this.terms[i].subtract(mvterm.terms[i]);
new_mvterm.sum = new_mvterm.sum.add(new_mvterm.terms[i]);
}
return new_mvterm;
};
MVTerm.prototype.multiply = function (mvterm) {
var c = this.coeff.multiply(mvterm.coeff),
l = this.terms.length,
new_mvterm = new MVTerm(c, [], this.map);
for(var i = 0; i < l; i++) {
new_mvterm.terms[i] = this.terms[i].add(mvterm.terms[i]);
new_mvterm.sum = new_mvterm.sum.add(new_mvterm.terms[i]);
}
return new_mvterm;
};
MVTerm.prototype.isZero = function () {
return this.coeff.equals(0);
};
MVTerm.prototype.toString = function () {
return '{ coeff: ' + this.coeff.toString() + ', terms: [' +
this.terms.join(',') + ']: sum: ' + this.sum.toString() + ', count: ' + this.count + '}';
};
core.Utils.toMapObj = function (arr) {
var c = 0, o = {};
for(var i = 0; i < arr.length; i++) {
var v = arr[i];
if(typeof o[v] === 'undefined') {
o[v] = c;
c++;
}
}
o.length = c;
return o;
};
core.Utils.filledArray = function (v, n, clss) {
var a = [];
while(n--) {
a[n] = clss ? new clss(v) : v;
}
return a;
};
core.Utils.arrSum = function (arr) {
var sum = 0, l = arr.length;
for(var i = 0; i < l; i++)
sum += arr[i];
return sum;
};
/**
* Determines if 2 arrays have intersecting elements.
* @param {Array} a
* @param {Array} b
* @returns {Boolean} True if a and b have intersecting elements.
*/
core.Utils.haveIntersection = function (a, b) {
var t;
if(b.length > a.length)
t = b, b = a, a = t; // indexOf to loop over shorter
return a.some(function (e) {
return b.indexOf(e) > -1;
});
};
/**
* Substitutes out functions as variables so they can be used in regular algorithms
* @param {Symbol} symbol
* @param {Object} map
* @returns {String} The expression string
*/
core.Utils.subFunctions = function (symbol, map) {
map = map || {};
var subbed = [];
symbol.each(function (x) {
if(x.group === FN || x.previousGroup === FN) {
//we need a new variable name so why not use one of the existing
var val = core.Utils.text(x, 'hash'), tvar = map[val];
if(!tvar) {
//generate a unique enough name
var t = x.fname + keys(map).length;
map[val] = t;
subbed.push(x.altVar(t));
}
else
subbed.push(x.altVar(tvar));
}
else if(x.group === CB || x.group === PL || x.group === CP) {
subbed.push(core.Utils.subFunctions(x, map));
}
else
subbed.push(x.text());
});
if(symbol.group === CP || symbol.group === PL)
return symbol.altVar(core.Utils.inBrackets(subbed.join('+')));
;
if(symbol.group === CB)
return symbol.altVar(core.Utils.inBrackets(subbed.join('*')));
return symbol.text();
};
core.Utils.getFunctionsSubs = function (map) {
var subs = {};
//prepare substitutions
for(var x in map)
subs[map[x]] = _.parse(x);
return subs;
};
var __ = core.Algebra = {
version: '1.4.6',
proots: function (symbol, decp) {
//the roots will be rounded up to 7 decimal places.
//if this causes trouble you can explicitly pass in a different number of places
//rarr for polynomial of power n is of format [n, coeff x^n, coeff x^(n-1), ..., coeff x^0]
decp = decp || 7;
var zeros = 0;
var known_roots = [];
var get_roots = function (rarr, powers, max) {
var roots = calcroots(rarr, powers, max).concat(known_roots);
for(var i = 0; i < zeros; i++)
roots.unshift(0);
return roots;
};
if(symbol instanceof Symbol && symbol.isPoly()) {
symbol.distributeMultiplier();
//make it so the symbol has a constants as the lowest term
if(symbol.group === PL) {
var lowest_pow = core.Utils.arrayMin(keys(symbol.symbols));
var lowest_symbol = symbol.symbols[lowest_pow].clone().toUnitMultiplier();
symbol = _.expand(_.divide(symbol, lowest_symbol));
known_roots.push(0); //add zero since this is a known root
}
if(symbol.group === core.groups.S) {
return [0];
}
else if(symbol.group === core.groups.PL) {
var powers = keys(symbol.symbols),
minpower = core.Utils.arrayMin(powers),
symbol = core.PARSER.divide(symbol, core.PARSER.parse(symbol.value + '^' + minpower));
}
var variable = keys(symbol.symbols).sort().pop(),
sym = symbol.group === core.groups.PL ? symbol.symbols : symbol.symbols[variable],
g = sym.group,
powers = g === S ? [sym.power.toDecimal()] : keys(sym.symbols),
rarr = [],
max = core.Utils.arrayMax(powers); //maximum power and degree of polynomial to be solved
// Prepare the data
for(var i = 1; i <= max; i++) {
var c = 0; //if there is no power then the hole must be filled with a zero
if(powers.indexOf(i + '') !== -1) {
if(g === S) {
c = sym.multiplier;
}
else {
c = sym.symbols[i].multiplier;
}
}
// Insert the coeffient but from the front
rarr.unshift(c);
}
rarr.push(symbol.symbols[CONST_HASH].multiplier);
if(sym.group === S)
rarr[0] = sym.multiplier;//the symbol maybe of group CP with one variable
return get_roots(rarr, powers, max);
}
else if(core.Utils.isArray(symbol)) {
var parr = symbol;
var rarr = [],
powers = [],
last_power = 0;
for(var i = 0; i < parr.length; i++) {
var coeff = parr[i][0],
pow = parr[i][1],
d = pow - last_power - 1;
//insert the zeros
for(var j = 0; j < d; j++)
rarr.unshift(0);
rarr.unshift(coeff);
if(pow !== 0)
powers.push(pow);
last_power = pow;
}
var max = Math.max.apply(undefined, powers);
return get_roots(rarr, powers, max);
}
else {
throw new core.exceptions.NerdamerTypeError('Cannot calculate roots. Symbol must be a polynomial!');
}
function calcroots(rarr, powers, max) {
var MAXDEGREE = 100; // Degree of largest polynomial accepted by this script.
// Make a clone of the coefficients before appending the max power
var p = rarr.slice(0);
// Divide the string up into its individual entries, which--presumably--are separated by whitespace
rarr.unshift(max);
if(max > MAXDEGREE) {
throw new core.exceptions.ValueLimitExceededError("This utility accepts polynomials of degree up to " + MAXDEGREE + ". ");
}
var zeroi = [], // Vector of imaginary components of roots
degreePar = {}; // degreePar is a dummy variable for passing the parameter POLYDEGREE by reference
degreePar.Degree = max;
for(i = 0; i < max; i++) {
zeroi.push(0);
}
var zeror = zeroi.slice(0); // Vector of real components of roots
// Find the roots
//--> Begin Jenkins-Traub
/*
* A verbatim copy of Mr. David Binner's Jenkins-Traub port
*/
function QuadSD_ak1(NN, u, v, p, q, iPar) {
// Divides p by the quadratic 1, u, v placing the quotient in q and the remainder in a, b
// iPar is a dummy variable for passing in the two parameters--a and b--by reference
q[0] = iPar.b = p[0];
q[1] = iPar.a = -(u * iPar.b) + p[1];
for(var i = 2; i < NN; i++) {
q[i] = -(u * iPar.a + v * iPar.b) + p[i];
iPar.b = iPar.a;
iPar.a = q[i];
}
return;
}
function calcSC_ak1(DBL_EPSILON, N, a, b, iPar, K, u, v, qk) {
// This routine calculates scalar quantities used to compute the next K polynomial and
// new estimates of the quadratic coefficients.
// calcSC - integer variable set here indicating how the calculations are normalized
// to avoid overflow.
// iPar is a dummy variable for passing in the nine parameters--a1, a3, a7, c, d, e, f, g, and h --by reference
// sdPar is a dummy variable for passing the two parameters--c and d--into QuadSD_ak1 by reference
var sdPar = new Object(),
// TYPE = 3 indicates the quadratic is almost a factor of K
dumFlag = 3;
// Synthetic division of K by the quadratic 1, u, v
sdPar.b = sdPar.a = 0.0;
QuadSD_ak1(N, u, v, K, qk, sdPar);
iPar.c = sdPar.a;
iPar.d = sdPar.b;
if(Math.abs(iPar.c) <= (100.0 * DBL_EPSILON * Math.abs(K[N - 1]))) {
if(Math.abs(iPar.d) <= (100.0 * DBL_EPSILON * Math.abs(K[N - 2])))
return dumFlag;
}
iPar.h = v * b;
if(Math.abs(iPar.d) >= Math.abs(iPar.c)) {
// TYPE = 2 indicates that all formulas are divided by d
dumFlag = 2;
iPar.e = a / (iPar.d);
iPar.f = (iPar.c) / (iPar.d);
iPar.g = u * b;
iPar.a3 = (iPar.e) * ((iPar.g) + a) + (iPar.h) * (b / (iPar.d));
iPar.a1 = -a + (iPar.f) * b;
iPar.a7 = (iPar.h) + ((iPar.f) + u) * a;
}
else {
// TYPE = 1 indicates that all formulas are divided by c;
dumFlag = 1;
iPar.e = a / (iPar.c);
iPar.f = (iPar.d) / (iPar.c);
iPar.g = (iPar.e) * u;
iPar.a3 = (iPar.e) * a + ((iPar.g) + (iPar.h) / (iPar.c)) * b;
iPar.a1 = -(a * ((iPar.d) / (iPar.c))) + b;
iPar.a7 = (iPar.g) * (iPar.d) + (iPar.h) * (iPar.f) + a;
}
return dumFlag;
}
function nextK_ak1(DBL_EPSILON, N, tFlag, a, b, iPar, K, qk, qp) {
// Computes the next K polynomials using the scalars computed in calcSC_ak1
// iPar is a dummy variable for passing in three parameters--a1, a3, and a7
var temp;
if(tFlag == 3) { // Use unscaled form of the recurrence
K[1] = K[0] = 0.0;
for(var i = 2; i < N; i++) {
K[i] = qk[i - 2];
}
return;
}
temp = ((tFlag == 1) ? b : a);
if(Math.abs(iPar.a1) > (10.0 * DBL_EPSILON * Math.abs(temp))) {
// Use scaled form of the recurrence
iPar.a7 /= iPar.a1;
iPar.a3 /= iPar.a1;
K[0] = qp[0];
K[1] = -(qp[0] * iPar.a7) + qp[1];
for(var i = 2; i < N; i++)
K[i] = -(qp[i - 1] * iPar.a7) + qk[i - 2] * iPar.a3 + qp[i];
}
else {
// If a1 is nearly zero, then use a special form of the recurrence
K[0] = 0.0;
K[1] = -(qp[0] * iPar.a7);
for(var i = 2; i < N; i++) {
K[i] = -(qp[i - 1] * iPar.a7) + qk[i - 2] * iPar.a3;
}
}
return;
}
function newest_ak1(tFlag, iPar, a, a1, a3, a7, b, c, d, f, g, h, u, v, K, N, p) {
// Compute new estimates of the quadratic coefficients using the scalars computed in calcSC_ak1
// iPar is a dummy variable for passing in the two parameters--uu and vv--by reference
// iPar.a = uu, iPar.b = vv
var a4, a5, b1, b2, c1, c2, c3, c4, temp;
iPar.b = iPar.a = 0.0;// The quadratic is zeroed
if(tFlag != 3) {
if(tFlag != 2) {
a4 = a + u * b + h * f;
a5 = c + (u + v * f) * d;
}
else {
a4 = (a + g) * f + h;
a5 = (f + u) * c + v * d;
}
// Evaluate new quadratic coefficients
b1 = -(K[N - 1] / p[N]);
b2 = -(K[N - 2] + b1 * p[N - 1]) / p[N];
c1 = v * b2 * a1;
c2 = b1 * a7;
c3 = b1 * b1 * a3;
c4 = -(c2 + c3) + c1;
temp = -c4 + a5 + b1 * a4;
if(temp != 0.0) {
iPar.a = -((u * (c3 + c2) + v * (b1 * a1 + b2 * a7)) / temp) + u;
iPar.b = v * (1.0 + c4 / temp);
}
}
return;
}
function Quad_ak1(a, b1, c, iPar) {
// Calculates the zeros of the quadratic a*Z^2 + b1*Z + c
// The quadratic formula, modified to avoid overflow, is used to find the larger zero if the
// zeros are real and both zeros are complex. The smaller real zero is found directly from
// the product of the zeros c/a.
// iPar is a dummy variable for passing in the four parameters--sr, si, lr, and li--by reference
var b, d, e;
iPar.sr = iPar.si = iPar.lr = iPar.li = 0.0;
if(a == 0) {
iPar.sr = ((b1 != 0) ? -(c / b1) : iPar.sr);
return;
}
if(c == 0) {
iPar.lr = -(b1 / a);
return;
}
// Compute discriminant avoiding overflow
b = b1 / 2.0;
if(Math.abs(b) < Math.abs(c)) {
e = ((c >= 0) ? a : -a);
e = -e + b * (b / Math.abs(c));
d = Math.sqrt(Math.abs(e)) * Math.sqrt(Math.abs(c));
}
else {
e = -((a / b) * (c / b)) + 1.0;
d = Math.sqrt(Math.abs(e)) * (Math.abs(b));
}
if(e >= 0) {
// Real zeros
d = ((b >= 0) ? -d : d);
iPar.lr = (-b + d) / a;
iPar.sr = ((iPar.lr != 0) ? (c / (iPar.lr)) / a : iPar.sr);
}
else {
// Complex conjugate zeros
iPar.lr = iPar.sr = -(b / a);
iPar.si = Math.abs(d / a);
iPar.li = -(iPar.si);
}
return;
}
function QuadIT_ak1(DBL_EPSILON, N, iPar, uu, vv, qp, NN, sdPar, p, qk, calcPar, K) {
// Variable-shift K-polynomial iteration for a quadratic factor converges only if the
// zeros are equimodular or nearly so.
// iPar is a dummy variable for passing in the five parameters--NZ, lzi, lzr, szi, and szr--by reference
// sdPar is a dummy variable for passing the two parameters--a and b--in by reference
// calcPar is a dummy variable for passing t