UNPKG

nerdamer

Version:

javascript light-weight symbolic math expression evaluator

1,312 lines (1,239 loc) 198 kB
/* * 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