UNPKG

nerdamer-ts

Version:

javascript light-weight symbolic math expression evaluator

1,299 lines (1,298 loc) 56.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.bigConvert = exports.symfunction = exports.Symbol = void 0; const decimal_js_1 = __importDefault(require("decimal.js")); const Settings_1 = require("../Settings"); const Utils_1 = require("../Core/Utils"); const Groups_1 = require("./Groups"); const Frac_1 = require("./Frac"); const bigInt_1 = __importDefault(require("../3rdparty/bigInt")); const Errors_1 = require("../Core/Errors"); const Math2_1 = require("../Functions/Math2"); const Text_1 = require("../Core/Text"); const LaTeX_1 = require("../LaTeX/LaTeX"); const Core_1 = require("../Functions/Core"); const expand_1 = require("../Functions/Core/math/expand"); const Trig_1 = require("../Functions/Trig"); const Utils_js_1 = require("../Core/Utils-js"); const Parser_1 = require("../Parser/Parser"); // noinspection JSUnusedGlobalSymbols /** * All symbols e.g. x, y, z, etc or functions are wrapped in this class. All symbols have a multiplier and a group. * All symbols except for "numbers (group Groups.N)" have a power. * @class Primary data type for the Parser. * @param {String | number} obj * * @property {number} power * @returns {Symbol} */ class Symbol { constructor(obj) { let isInfinity = obj === 'Infinity'; // Convert big numbers to a string if (obj instanceof decimal_js_1.default) { obj = obj.toString(); } //define numeric symbols if (/^(-?\+?\d+)\.?\d*e?-?\+?\d*/i.test(obj) || obj instanceof decimal_js_1.default) { this.group = Groups_1.Groups.N; this.value = Settings_1.Settings.CONST_HASH; this.multiplier = new Frac_1.Frac(obj); } //define symbolic symbols else { this.group = Groups_1.Groups.S; (0, Utils_1.validateName)(obj); this.value = obj; this.multiplier = new Frac_1.Frac(1); this.imaginary = obj === Settings_1.Settings.IMAGINARY; this.isInfinity = isInfinity; } //As of 6.0.0 we switched to infinite precision so all objects have a power //Although this is still redundant in constants, it simplifies the logic in //other parts so we'll keep it this.power = new Frac_1.Frac(1); // Added to silence the strict warning. return this; } /** * Returns vanilla imaginary symbol * @returns {Symbol} */ static imaginary() { let s = new Symbol(Settings_1.Settings.IMAGINARY); s.imaginary = true; return s; } /** * Return nerdamer's representation of Infinity * @param {int} negative -1 to return negative infinity * @returns {Symbol} */ static infinity(negative = 1) { let v = new Symbol('Infinity'); if (negative === -1) v.negate(); return v; } static shell(group, value) { let symbol = new Symbol(value); symbol.group = group; symbol.symbols = {}; symbol.length = 0; return symbol; } //sqrt(x) -> x^(1/2) static unwrapSQRT(symbol, all) { let p = symbol.power; if (symbol.fname === Settings_1.Settings.SQRT && (symbol.isLinear() || all)) { let t = symbol.args[0].clone(); t.power = t.power.multiply(new Frac_1.Frac(1 / 2)); t.multiplier = t.multiplier.multiply(symbol.multiplier); symbol = t; if (all) symbol.power = p.multiply(new Frac_1.Frac(1 / 2)); } return symbol; } static hyp(a, b) { a = a || new Symbol(0); b = b || new Symbol(0); return (0, Core_1.sqrt)((0, Core_1.add)((0, Core_1.pow)(a.clone(), new Symbol(2)), (0, Core_1.pow)(b.clone(), new Symbol(2)))); } //converts to polar form array static toPolarFormArray(symbol) { let re, im, r, theta; re = symbol.realpart(); im = symbol.imagpart(); r = Symbol.hyp(re, im); theta = re.equals(0) ? (0, Parser_1.parse)('pi/2') : Trig_1.Trig.atan((0, Core_1.divide)(im, re)); return [r, theta]; } //removes parentheses static unwrapPARENS(symbol) { if (symbol.fname === '') { let r = symbol.args[0]; r.power = r.power.multiply(symbol.power); r.multiplier = r.multiplier.multiply(symbol.multiplier); if (symbol.fname === '') return Symbol.unwrapPARENS(r); return r; } return symbol; } ; //quickly creates a Symbol static create(value, power) { power = power === undefined ? 1 : power; return (0, Parser_1.parse)('(' + value + ')^(' + power + ')'); } /** * Gets nth root accounting for rounding errors * @param {Number} n * @return {Number} */ getNth(n) { // First calculate the root let root = (0, Parser_1.evaluate)((0, Core_1.pow)((0, Parser_1.parse)(this.multiplier), (0, Parser_1.parse)(n).invert())); // Round of any errors let rounded = (0, Parser_1.parse)((0, Utils_1.nround)(root)); // Reverse the root let e = (0, Parser_1.evaluate)((0, Core_1.pow)(rounded, (0, Parser_1.parse)(n))); // If the rounded root equals the original number then we're good if (e.equals((0, Parser_1.parse)(this.multiplier))) { return rounded; } // Otherwise return the unrounded version return root; } /** * Checks if symbol is to the nth power * @returns {Boolean} */ isToNth(n) { // Start by check in the multiplier for squareness // First get the root but round it because currently we still depend let root = this.getNth(n); let nthMultiplier = (0, Utils_1.isInt)(root); let nthPower; if (this.group === Groups_1.Groups.CB) { // Start by assuming that all will be square. nthPower = true; // All it takes is for one of the symbols to not have an even power // e.g. x^n1*y^n2 requires that both n1 and n2 are even this.each(function (x) { let isNth = x.isToNth(n); if (!isNth) { nthPower = false; } }); } else { // Check if the power is divisible by n if it's not a number. nthPower = this.group === Groups_1.Groups.N ? true : (0, Utils_1.isInt)((0, Core_1.divide)((0, Parser_1.parse)(this.power), (0, Parser_1.parse)(n))); } return nthMultiplier && nthPower; } /** * Checks if a symbol is square * @return {Boolean} */ isSquare() { return this.isToNth(2); } /** * Checks if a symbol is cube * @return {Boolean} */ isCube() { return this.isToNth(3); } /** * Checks if a symbol is a bare variable * @return {Boolean} */ isSimple() { return this.power.equals(1) && this.multiplier.equals(1); } /** * Simplifies the power of the symbol * @returns {Symbol} a clone of the symbol */ powSimp() { if (this.group === Groups_1.Groups.CB) { let powers = []; this.each(function (x) { let p = x.power; //why waste time if I can't do anything anyway if ((0, Utils_1.isSymbol)(p) || p.equals(1)) return this.clone(); powers.push(p); }); let min = new Frac_1.Frac((0, Utils_1.arrayMin)(powers)); //handle the coefficient //handle the multiplier let sign = this.multiplier.sign(); let m = this.multiplier.clone().abs(), mfactors = Math2_1.Math2.ifactor(m); //if we have a multiplier of 6750 and a min of 2 then the factors are 5^3*5^3*2 //we can then reduce it to 2*3*5*(15)^2 let out_ = new Frac_1.Frac(1); let in_ = new Frac_1.Frac(1); for (let x in mfactors) { let n = new Frac_1.Frac(mfactors[x]); if (!n.lessThan(min)) { n = n.divide(min).subtract(new Frac_1.Frac(1)); in_ = in_.multiply(new Frac_1.Frac(x)); //move the factor inside the bracket } out_ = out_.multiply((0, Parser_1.parse)((0, Utils_1.inBrackets)(x) + '^' + (0, Utils_1.inBrackets)(n)).multiplier); } let t = new Symbol(in_); this.each(function (x) { x = x.clone(); x.power = x.power.divide(min); t = (0, Core_1.multiply)(t, x); }); let xt = symfunction(Settings_1.Settings.PARENTHESIS, [t]); xt.power = min; xt.multiplier = sign < 0 ? out_.negate() : out_; return xt; } return this.clone(); } /** * Checks to see if two functions are of equal value * @param {Symbol} symbol */ equals(symbol) { if (!(0, Utils_1.isSymbol)(symbol)) symbol = new Symbol(symbol); return this.value === symbol.value && this.power.equals(symbol.power) && this.multiplier.equals(symbol.multiplier) && this.group === symbol.group; } abs() { let e = this.clone(); e.multiplier.abs(); return e; } // Greater than gt(symbol) { if (!(0, Utils_1.isSymbol)(symbol)) symbol = new Symbol(symbol); return this.isConstant() && symbol.isConstant() && this.multiplier.greaterThan(symbol.multiplier); } // Greater than gte(symbol) { if (!(0, Utils_1.isSymbol)(symbol)) symbol = new Symbol(symbol); return this.equals(symbol) || this.isConstant() && symbol.isConstant() && this.multiplier.greaterThan(symbol.multiplier); } // Less than lt(symbol) { if (!(0, Utils_1.isSymbol)(symbol)) symbol = new Symbol(symbol); return this.isConstant() && symbol.isConstant() && this.multiplier.lessThan(symbol.multiplier); } // Less than lte(symbol) { if (!(0, Utils_1.isSymbol)(symbol)) symbol = new Symbol(symbol); return this.equals(symbol) || this.isConstant() && symbol.isConstant() && this.multiplier.lessThan(symbol.multiplier); } /** * Because nerdamer doesn't group symbols by polynomials but * rather a custom grouping method, this has to be * reinserted in order to make use of most algorithms. This function * checks if the symbol meets the criteria of a polynomial. * @param {boolean} multivariate * @returns {boolean} */ isPoly(multivariate = false) { let g = this.group, p = this.power; //the power must be a integer so fail if it's not if (!(0, Utils_1.isInt)(p) || p < 0) return false; //constants and first orders if (g === Groups_1.Groups.N || g === Groups_1.Groups.S || this.isConstant(true)) return true; let vars = this.variables(); if (g === Groups_1.Groups.CB && vars.length === 1) { //the variable is assumed the only one that was found let v = vars[0]; //if no variable then guess what!?!? We're done!!! We have a polynomial. if (!v) return true; for (let x in this.symbols) { let sym = this.symbols[x]; //sqrt(x) if (sym.group === Groups_1.Groups.FN && !sym.args[0].isConstant()) return false; if (!sym.contains(v) && !sym.isConstant(true)) return false; } return true; } //PL groups. These only fail if a power is not an int //this should handle cases such as x^2*t if (this.isComposite() || g === Groups_1.Groups.CB && multivariate) { //fail if we're not checking for multivariate polynomials if (!multivariate && vars.length > 1) return false; //loop though the symbols and check if they qualify for (let x in this.symbols) { //we've already the symbols if we're not checking for multivariates at this point //so we check the sub-symbols if (!this.symbols[x].isPoly(multivariate)) return false; } return true; } else return false; /* //all tests must have passed so we must be dealing with a polynomial return true; */ } //removes the requested variable from the symbol and returns the remainder stripVar(x, exclude_x) { let retval; if ((this.group === Groups_1.Groups.PL || this.group === Groups_1.Groups.S) && this.value === x) retval = new Symbol(exclude_x ? 0 : this.multiplier); else if (this.group === Groups_1.Groups.CB && this.isLinear()) { retval = new Symbol(1); this.each(function (s) { if (!s.contains(x, true)) retval = (0, Core_1.multiply)(retval, s.clone()); }); retval.multiplier = retval.multiplier.multiply(this.multiplier); } else if (this.group === Groups_1.Groups.CP && !this.isLinear()) { retval = new Symbol(this.multiplier); } else if (this.group === Groups_1.Groups.CP && this.isLinear()) { retval = new Symbol(0); this.each(function (s) { if (!s.contains(x)) { let t = s.clone(); t.multiplier = t.multiplier.multiply(this.multiplier); retval = (0, Core_1.add)(retval, t); } }); //BIG TODO!!! It doesn't make much sense if (retval.equals(0)) retval = new Symbol(this.multiplier); } else if (this.group === Groups_1.Groups.EX && this.power.contains(x, true)) { retval = new Symbol(this.multiplier); } else if (this.group === Groups_1.Groups.FN && this.contains(x)) { retval = new Symbol(this.multiplier); } else //wth? This should technically be the multiplier. //Unfortunately this method wasn't very well thought out :`(. //should be: retval = new Symbol(this.multiplier); //use: ((1+x^2)*sqrt(-1+x^2))^(-1) for correction. //this will break a bunch of unit tests so be ready to for the long haul retval = this.clone(); return retval; } //returns symbol in array form with x as base e.g. a*x^2+b*x+c = [c, b, a]. toArray(v, arr) { arr = arr || { arr: [], add: function (x, idx) { let e = this.arr[idx]; this.arr[idx] = e ? (0, Core_1.add)(e, x) : x; } }; let g = this.group; if (g === Groups_1.Groups.S && this.contains(v)) { arr.add(new Symbol(this.multiplier), this.power); } else if (g === Groups_1.Groups.CB) { let a = this.stripVar(v), x = (0, Core_1.divide)(this.clone(), a.clone()); let p = x.isConstant() ? 0 : x.power; arr.add(a, p); } else if (g === Groups_1.Groups.PL && this.value === v) { this.each(function (x, p) { arr.add(x.stripVar(v), p); }); } else if (g === Groups_1.Groups.CP) { //the logic: they'll be broken into symbols so e.g. (x^2+x)+1 or (a*x^2+b*x+c) //each case is handled above this.each(function (x) { x.toArray(v, arr); }); } else if (this.contains(v)) { throw new Errors_1.NerdamerTypeError('Cannot convert to array! Exiting'); } else { arr.add(this.clone(), 0); //it's just a constant wrt to v } //fill the holes arr = arr.arr; //keep only the array since we don't need the object anymore for (let i = 0; i < arr.length; i++) if (!arr[i]) arr[i] = new Symbol(0); return arr; } //checks to see if a symbol contans a function hasFunc(v) { let fn_group = this.group === Groups_1.Groups.FN || this.group === Groups_1.Groups.EX; if (fn_group && !v || fn_group && this.contains(v)) return true; if (this.symbols) { for (let x in this.symbols) { if (this.symbols[x].hasFunc(v)) return true; } } return false; } sub(a, b) { a = !(0, Utils_1.isSymbol)(a) ? (0, Parser_1.parse)(a) : a.clone(); b = !(0, Utils_1.isSymbol)(b) ? (0, Parser_1.parse)(b) : b.clone(); if (a.group === Groups_1.Groups.N || a.group === Groups_1.Groups.P) (0, Errors_1.err)('Cannot substitute a number. Must be a variable'); let same_pow = false, a_is_unit_multiplier = a.multiplier.equals(1), m = this.multiplier.clone(), retval; /* * In order to make the substitution the bases have to first match take * (x+1)^x -> (x+1)=y || x^2 -> x=y^6 * In both cases the first condition is that the bases match so we begin there * Either both are Groups.PL or both are not Groups.PL but we cannot have Groups.PL and a non-PL group match */ if (this.value === a.value && (this.group !== Groups_1.Groups.PL && a.group !== Groups_1.Groups.PL || this.group === Groups_1.Groups.PL && a.group === Groups_1.Groups.PL)) { //we cleared the first hurdle but a subsitution may not be possible just yet if (a_is_unit_multiplier || a.multiplier.equals(this.multiplier)) { if (a.isLinear()) { retval = b; } else if (a.power.equals(this.power)) { retval = b; same_pow = true; } if (a.multiplier.equals(this.multiplier)) m = new Frac_1.Frac(1); } } //the next thing is to handle CB else if (this.group === Groups_1.Groups.CB || this.previousGroup === Groups_1.Groups.CB) { retval = new Symbol(1); this.each(function (x) { let subbed = (0, Parser_1.parse)(x.sub(a, b)); //parse it again for safety retval = (0, Core_1.multiply)(retval, subbed); }); } else if (this.isComposite()) { let symbol = this.clone(); if (a.isComposite() && symbol.isComposite() && symbol.isLinear() && a.isLinear()) { let find = function (stack, needle) { for (let x in stack.symbols) { let sym = stack.symbols[x]; //if the symbol equals the needle or it's within the sub-symbols we're done if (sym.isComposite() && find(sym, needle) || sym.equals(needle)) return true; } return false; }; //go fish for (let x in a.symbols) { if (!find(symbol, a.symbols[x])) return symbol.clone(); } retval = (0, Core_1.add)((0, Core_1.subtract)(symbol.clone(), a), b); } else { retval = new Symbol(0); symbol.each(function (x) { retval = (0, Core_1.add)(retval, x.sub(a, b)); }); } } else if (this.group === Groups_1.Groups.EX) { // the parsed value could be a function so parse and sub retval = (0, Parser_1.parse)(this.value).sub(a, b); } else if (this.group === Groups_1.Groups.FN) { let nargs = []; for (let i = 0; i < this.args.length; i++) { let arg = this.args[i]; if (!(0, Utils_1.isSymbol)(arg)) arg = (0, Parser_1.parse)(arg); nargs.push(arg.sub(a, b)); } retval = symfunction(this.fname, nargs); } //if we did manage a substitution if (retval) { if (!same_pow) { //substitute the power let p = this.group === Groups_1.Groups.EX ? this.power.sub(a, b) : (0, Parser_1.parse)(this.power); //now raise the symbol to that power retval = (0, Core_1.pow)(retval, p); } //transfer the multiplier retval.multiplier = retval.multiplier.multiply(m); //done return retval; } //if all else fails return this.clone(); } isMonomial() { if (this.group === Groups_1.Groups.S) return true; if (this.group === Groups_1.Groups.CB) { for (let x in this.symbols) if (this.symbols[x].group !== Groups_1.Groups.S) return false; } else return false; return true; } isPi() { return this.group === Groups_1.Groups.S && this.value === 'pi'; } sign() { return this.multiplier.sign(); } isE() { return this.value === 'e'; } isSQRT() { return this.fname === Settings_1.Settings.SQRT; } isConstant(check_all, check_symbols) { if (check_symbols && this.group === Groups_1.Groups.CB) { for (let x in this.symbols) { if (this.symbols[x].isConstant(true)) return true; } } if (check_all === 'functions' && this.isComposite()) { let isConstant = true; this.each(function (x) { if (!x.isConstant(check_all, check_symbols)) { isConstant = false; } }, true); return isConstant; } if (check_all === 'all' && (this.isPi() || this.isE())) { return true; } if (check_all && this.group === Groups_1.Groups.FN) { for (let i = 0; i < this.args.length; i++) { if (!this.args[i].isConstant(check_all)) return false; } return true; } if (check_all) return (0, Utils_js_1.isNumericSymbol)(this); return this.value === Settings_1.Settings.CONST_HASH; } //the symbols is imaginary if //1. n*i //2. a+b*i //3. a*i isImaginary() { if (this.imaginary) return true; else if (this.symbols) { for (let x in this.symbols) if (this.symbols[x].isImaginary()) return true; } return false; } /** * Returns the real part of a symbol * @returns {Symbol} */ realpart() { if (this.isConstant()) { return this.clone(); } else if (this.imaginary) return new Symbol(0); else if (this.isComposite()) { let retval = new Symbol(0); this.each(function (x) { retval = (0, Core_1.add)(retval, x.realpart()); }); return retval; } else if (this.isImaginary()) return new Symbol(0); return this.clone(); } /* * Return imaginary part of a symbol * @returns {Symbol} */ imagpart() { if (this.group === Groups_1.Groups.S && this.isImaginary()) return new Symbol(this.multiplier); if (this.isComposite()) { let retval = new Symbol(0); this.each(function (x) { retval = (0, Core_1.add)(retval, x.imagpart()); }); return retval; } if (this.group === Groups_1.Groups.CB) return this.stripVar(Settings_1.Settings.IMAGINARY); return new Symbol(0); } isInteger() { return this.isConstant() && this.multiplier.isInteger(); } isLinear(wrt) { if (wrt) { if (this.isConstant()) return true; if (this.group === Groups_1.Groups.S) { if (this.value === wrt) return this.power.equals(1); else return true; } if (this.isComposite() && this.power.equals(1)) { for (let x in this.symbols) { if (!this.symbols[x].isLinear(wrt)) return false; } return true; } if (this.group === Groups_1.Groups.CB && this.symbols[wrt]) return this.symbols[wrt].isLinear(wrt); return false; } else return this.power.equals(1); } /** * Checks to see if a symbol has a function by a specified name or within a specified list * @param {String|String[]} names * @returns {Boolean} */ containsFunction(names) { if (typeof names === 'string') names = [names]; if (this.group === Groups_1.Groups.FN && names.indexOf(this.fname) !== -1) return true; if (this.symbols) { for (let x in this.symbols) { if (this.symbols[x].containsFunction(names)) return true; } } return false; } multiplyPower(p2) { //leave out 1 if (this.group === Groups_1.Groups.N && this.multiplier.equals(1)) return this; let p1 = this.power; if (this.group !== Groups_1.Groups.EX && p2.group === Groups_1.Groups.N) { let p = p2.multiplier; if (this.group === Groups_1.Groups.N && !p.isInteger()) { this.convert(Groups_1.Groups.P); } this.power = p1.equals(1) ? p.clone() : p1.multiply(p); if (this.group === Groups_1.Groups.P && (0, Utils_1.isInt)(this.power)) { //bring it back to an N this.value = Math.pow(this.value, this.power); this.toLinear(); this.convert(Groups_1.Groups.N); } } else { if (this.group !== Groups_1.Groups.EX) { p1 = new Symbol(p1); this.convert(Groups_1.Groups.EX); } this.power = (0, Core_1.multiply)(p1, p2); } return this; } setPower(p, retainSign) { //leave out 1 if (this.group === Groups_1.Groups.N && this.multiplier.equals(1)) { return this; } if (this.group === Groups_1.Groups.EX && !(0, Utils_1.isSymbol)(p)) { this.group = this.previousGroup; delete this.previousGroup; if (this.group === Groups_1.Groups.N) { this.multiplier = new Frac_1.Frac(this.value); this.value = Settings_1.Settings.CONST_HASH; } else this.power = p; } else { let isSymbolic = false; if ((0, Utils_1.isSymbol)(p)) { if (p.group === Groups_1.Groups.N) { //p should be the multiplier instead p = p.multiplier; } else { isSymbolic = true; } } let group = isSymbolic ? Groups_1.Groups.EX : Groups_1.Groups.P; this.power = p; if (this.group === Groups_1.Groups.N && group) this.convert(group, retainSign); } return this; } /** * Checks to see if symbol is located in the denominator * @returns {boolean} */ isInverse() { if (this.group === Groups_1.Groups.EX) return (this.power.multiplier.lessThan(0)); return this.power < 0; } /** * Make a duplicate of a symbol by copying a predefined list of items. * The name 'copy' would probably be a more appropriate name. * to a new symbol * @param {Symbol | undefined} c * @returns {Symbol} */ clone(c = undefined) { let clone = c || new Symbol(0), //list of properties excluding power as this may be a symbol and would also need to be a clone. properties = [ 'value', 'group', 'length', 'previousGroup', 'imaginary', 'fname', 'args', 'isInfinity', 'scientific' ], l = properties.length, i; if (this.symbols) { clone.symbols = {}; for (let x in this.symbols) { clone.symbols[x] = this.symbols[x].clone(); } } for (i = 0; i < l; i++) { if (this[properties[i]] !== undefined) { clone[properties[i]] = this[properties[i]]; } } clone.power = this.power.clone(); clone.multiplier = this.multiplier.clone(); //add back the flag to track if this symbol is a conversion symbol if (this.isConversion) clone.isConversion = this.isConversion; if (this.isUnit) clone.isUnit = this.isUnit; return clone; } /** * Converts a symbol multiplier to one. * @param {Boolean} keepSign Keep the multiplier as negative if the multiplier is negative and keepSign is true * @returns {Symbol} */ toUnitMultiplier(keepSign = false) { this.multiplier.num = new bigInt_1.default(this.multiplier.num.isNegative() && keepSign ? -1 : 1); this.multiplier.den = new bigInt_1.default(1); return this; } /** * Converts a Symbol's power to one. * @returns {Symbol} */ toLinear() { // Do nothing if it's already linear if (this.power.equals(1)) { return this; } this.setPower(new Frac_1.Frac(1)); return this; } /** * Iterates over all the sub-symbols. If no sub-symbols exist then it's called on itself * @param {Function} fn * @@param {Boolean} deep If true it will itterate over the sub-symbols their symbols as well * @param deep */ each(fn, deep) { if (!this.symbols) { fn.call(this, this, this.value); } else { for (let x in this.symbols) { let sym = this.symbols[x]; if (sym.group === Groups_1.Groups.PL && deep) { for (let y in sym.symbols) { fn.call(x, sym.symbols[y], y); } } else fn.call(this, sym, x); } } } /** * A numeric value to be returned for Javascript. It will try to * return a number as far a possible but in case of a pure symbolic * symbol it will just return its text representation * @returns {String|Number} */ valueOf() { if (this.group === Groups_1.Groups.N) return this.multiplier.valueOf(); else if (this.power === 0) { return 1; } else if (this.multiplier === 0) { return 0; } else { return (0, Text_1.text)(this, 'decimals'); } } /** * Checks to see if a symbols has a particular variable within it. * Pass in true as second argument to include the power of exponentials * which aren't check by default. * @example let s = _.parse('x+y+z'); s.contains('y'); * //returns true * @param {any} variable * @param {boolean} all * @returns {boolean} */ contains(variable, all) { //contains expects a string variable = String(variable); let g = this.group; if (this.value === variable) return true; if (this.symbols) { for (let x in this.symbols) { if (this.symbols[x].contains(variable, all)) return true; } } if (g === Groups_1.Groups.FN || this.previousGroup === Groups_1.Groups.FN) { for (let i = 0; i < this.args.length; i++) { if (this.args[i].contains(variable, all)) return true; } } if (g === Groups_1.Groups.EX) { //exit only if it does if (all && this.power.contains(variable, all)) { return true; } if (this.value === variable) return true; } return this.value === variable; } /** * Negates a symbols * @returns {boolean} */ negate() { this.multiplier.negate(); if (this.group === Groups_1.Groups.CP || this.group === Groups_1.Groups.PL) this.distributeMultiplier(); return this; } /** * Inverts a symbol * @param {boolean} power_only * @param {boolean} all * @returns {boolean} */ invert(power_only, all) { //invert the multiplier if (!power_only) this.multiplier = this.multiplier.invert(); //invert the rest if ((0, Utils_1.isSymbol)(this.power)) { this.power.negate(); } else if (this.group === Groups_1.Groups.CB && all) { this.each(function (x) { return x.invert(); }); } else { if (this.power && this.group !== Groups_1.Groups.N) this.power.negate(); } return this; } /** * Symbols of group Groups.CP or Groups.PL may have the multiplier being carried by * the top level symbol at any given time e.g. 2*(x+y+z). This is * convenient in many cases, however in some cases the multiplier needs * to be carried individually e.g. 2*x+2*y+2*z. * This method distributes the multiplier over the entire symbol * @param {boolean} all * @returns {Symbol} */ distributeMultiplier(all = false) { let is_one = all ? this.power.absEquals(1) : this.power.equals(1); if (this.symbols && is_one && this.group !== Groups_1.Groups.CB && !this.multiplier.equals(1)) { for (let x in this.symbols) { let s = this.symbols[x]; s.multiplier = s.multiplier.multiply(this.multiplier); s.distributeMultiplier(); } this.toUnitMultiplier(); } return this; } /** * This method expands the exponent over the entire symbol just like * distributeMultiplier * @returns {Symbol} */ distributeExponent() { if (!this.power.equals(1)) { let p = this.power; for (let x in this.symbols) { let s = this.symbols[x]; if (s.group === Groups_1.Groups.EX) { s.power = (0, Core_1.multiply)(s.power, new Symbol(p)); } else { this.symbols[x].power = this.symbols[x].power.multiply(p); } } this.toLinear(); } return this; } /** * This method will attempt to up-convert or down-convert one symbol * from one group to another. Not all symbols are convertible from one * group to another however. In that case the symbol will remain * unchanged. * @param {number} group * @param {string} imaginary */ convert(group, imaginary = undefined) { if (group > Groups_1.Groups.FN) { //make a clone of this symbol; let cp = this.clone(); //attach a symbols object and upgrade the group this.symbols = {}; if (group === Groups_1.Groups.CB) { //symbol of group Groups.CB hold symbols bound together through multiplication //because of commutativity this multiplier can technically be anywhere within the group //to keep track of it however it's easier to always have the top level carry it cp.toUnitMultiplier(); } else { //reset the symbol this.toUnitMultiplier(); } if (this.group === Groups_1.Groups.FN) { cp.args = this.args; delete this.args; delete this.fname; } //the symbol may originate from the symbol i but this property no longer holds true //after copying if (this.isImgSymbol) delete this.isImgSymbol; this.toLinear(); //attach a clone of this symbol to the symbols object using its proper key this.symbols[cp.keyForGroup(group)] = cp; this.group = group; //objects by default don't have a length property. However, in order to keep track of the number //of sub-symbols we have to impliment our own. this.length = 1; } else if (group === Groups_1.Groups.EX) { //1^x is just one so check and make sure if (!(this.group === Groups_1.Groups.N && this.multiplier.equals(1))) { if (this.group !== Groups_1.Groups.EX) this.previousGroup = this.group; if (this.group === Groups_1.Groups.N) { this.value = this.multiplier.num.toString(); this.toUnitMultiplier(); } //update the hash to reflect the accurate hash else this.value = (0, Text_1.text)(this, 'hash'); this.group = Groups_1.Groups.EX; } } else if (group === Groups_1.Groups.N) { let m = this.multiplier.toDecimal(); if (this.symbols) this.symbols = undefined; new Symbol(this.group === Groups_1.Groups.P ? m * Math.pow(this.value, this.power) : m).clone(this); } else if (group === Groups_1.Groups.P && this.group === Groups_1.Groups.N) { this.value = imaginary ? this.multiplier.num.toString() : Math.abs(this.multiplier.num.toString()); this.toUnitMultiplier(!imaginary); this.group = Groups_1.Groups.P; } return this; } /** * This method is one of the principal methods to make it all possible. * It performs cleanup and prep operations whenever a symbols is * inserted. If the symbols results in a 1 in a Groups.CB (multiplication) * group for instance it will remove the redundant symbol. Similarly * in a symbol of group Groups.PL or Groups.CP (symbols glued by multiplication) it * will remove any dangling zeroes from the symbol. It will also * up-convert or down-convert a symbol if it detects that it's * incorrectly grouped. It should be noted that this method is not * called directly but rather by the 'attach' method for addition groups * and the 'combine' method for multiplication groups. * @param {Symbol} symbol * @param {String} action */ insert(symbol, action) { //this check can be removed but saves a lot of aggravation when trying to hunt down //a bug. If left, you will instantly know that the error can only be between 2 symbols. if (!(0, Utils_1.isSymbol)(symbol)) (0, Errors_1.err)('Object ' + symbol + ' is not of type Symbol!'); if (this.symbols) { let group = this.group; if (group > Groups_1.Groups.FN) { let key = symbol.keyForGroup(group); let existing = key in this.symbols ? this.symbols[key] : false; //check if there's already a symbol there if (action === 'add') { let hash = key; if (existing) { //add them together using the parser this.symbols[hash] = (0, Core_1.add)(existing, symbol); //if the addition resulted in a zero multiplier remove it if (this.symbols[hash].multiplier.equals(0)) { delete this.symbols[hash]; this.length--; if (this.length === 0) { this.convert(Groups_1.Groups.N); this.multiplier = new Frac_1.Frac(0); } } } else { this.symbols[key] = symbol; this.length++; } } else { //check if this is of group Groups.P and unwrap before inserting if (symbol.group === Groups_1.Groups.P && (0, Utils_1.isInt)(symbol.power)) { symbol.convert(Groups_1.Groups.N); } //transfer the multiplier to the upper symbol but only if the symbol numeric if (symbol.group !== Groups_1.Groups.EX) { this.multiplier = this.multiplier.multiply(symbol.multiplier); symbol.toUnitMultiplier(); } else { symbol.parens = symbol.multiplier.lessThan(0); this.multiplier = this.multiplier.multiply(symbol.multiplier.clone().abs()); symbol.toUnitMultiplier(true); } if (existing) { //remove because the symbol may have changed symbol = (0, Core_1.multiply)((0, Utils_1.remove)(this.symbols, key), symbol); if (symbol.isConstant()) { this.multiplier = this.multiplier.multiply(symbol.multiplier); symbol = new Symbol(1); //the dirty work gets done down the line when it detects 1 } this.length--; //clean up } //don't insert the symbol if it's 1 if (!symbol.isOne(true)) { this.symbols[key] = symbol; this.length++; } else if (symbol.multiplier.lessThan(0)) { this.negate(); //put back the sign } } //clean up if (this.length === 0) this.convert(Groups_1.Groups.N); //update the hash if (this.group === Groups_1.Groups.CP || this.group === Groups_1.Groups.CB) { this.updateHash(); } } } return this; } //the insert method for addition attach(symbol) { if (Array.isArray(symbol)) { for (let i = 0; i < symbol.length; i++) this.insert(symbol[i], 'add'); return this; } return this.insert(symbol, 'add'); } //the insert method for multiplication combine(symbol) { if (Array.isArray(symbol)) { for (let i = 0; i < symbol.length; i++) this.insert(symbol[i], 'multiply'); return this; } return this.insert(symbol, 'multiply'); } /** * This method should be called after any major "surgery" on a symbol. * It updates the hash of the symbol for example if the fname of a * function has changed it will update the hash of the symbol. */ updateHash() { if (this.group === Groups_1.Groups.N) return; if (this.group === Groups_1.Groups.FN) { let contents = '', args = this.args, is_parens = this.fname === Settings_1.Settings.PARENTHESIS; for (let i = 0; i < args.length; i++) contents += (i === 0 ? '' : ',') + (0, Text_1.text)(args[i]); let fn_name = is_parens ? '' : this.fname; this.value = fn_name + (is_parens ? contents : (0, Utils_1.inBrackets)(contents)); } else if (!(this.group === Groups_1.Groups.S || this.group === Groups_1.Groups.PL)) { this.value = (0, Text_1.text)(this, 'hash'); } } /** * this function defines how every group in stored within a group of * higher order think of it as the switchboard for the library. It * defines the hashes for symbols. * @param {int} group */ keyForGroup(group) { let g = this.group; let key; if (g === Groups_1.Groups.N) { key = this.value; } else if (g === Groups_1.Groups.S || g === Groups_1.Groups.P) { if (group === Groups_1.Groups.PL) key = this.power.toDecimal(); else key = this.value; } else if (g === Groups_1.Groups.FN) { if (group === Groups_1.Groups.PL) key = this.power.toDecimal(); else key = (0, Text_1.text)(this, 'hash'); } else if (g === Groups_1.Groups.PL) { //if the order is reversed then we'll assume multiplication //TODO: possible future dilemma if (group === Groups_1.Groups.CB) key = (0, Text_1.text)(this, 'hash'); else if (group === Groups_1.Groups.CP) { if (this.power.equals(1)) key = this.value; else key = (0, Utils_1.inBrackets)((0, Text_1.text)(this, 'hash')) + Settings_1.Settings.POWER_OPERATOR + this.power.toDecimal(); } else if (group === Groups_1.Groups.PL) key = this.power.toString(); else key = this.value; return key; } else if (g === Groups_1.Groups.CP) { if (group === Groups_1.Groups.CP) { key = (0, Text_1.text)(this, 'hash'); } if (group === Groups_1.Groups.PL) key = this.power.toDecimal(); else key = this.value; } else if (g === Groups_1.Groups.CB) { if (group === Groups_1.Groups.PL) key = this.power.toDecimal(); else key = (0, Text_1.text)(this, 'hash'); } else if (g === Groups_1.Groups.EX) { if (group === Groups_1.Groups.PL) key = (0, Text_1.text)(this.power); else key = (0, Text_1.text)(this, 'hash'); } return key; } /** * Symbols are typically stored in an object which works fine for most * cases but presents a problem when the order of the symbols makes * a difference. This function simply collects all the symbols and * returns them as an array. If a function is supplied then that * function is called on every symbol contained within the object. * @param {Function} fn * @param {Object} opt * @param {Function} sort_fn * @@param {Boolean} expand_symbol * @param expand_symbol * @returns {Array} */ collectSymbols(fn, opt, sort_fn = undefined, expand_symbol = false) { let collected = []; if (!this.symbols) collected.push(this); else { for (let x in this.symbols) { let symbol = this.symbols[x]; if (expand_symbol && (symbol.group === Groups_1.Groups.PL || symbol.group === Groups_1.Groups.CP)) { collected = collected.concat(symbol.collectSymbols()); } else collected.push(fn ? fn(symbol, opt) : symbol); } } if (sort_fn === null) sort_fn = undefined; //WTF Firefox? Seriously? return collected.sort(sort_fn); //sort hopefully gives us some sort of consistency } /** * Returns the latex representation of the symbol * @param {String} option * @returns {String} */ latex(option) { return LaTeX_1.LaTeX.latex(this, option); } /** * Returns the text representation of a symbol * @param {String} option * @returns {String} */ text(option = undefined) { return (0, Text_1.text)(this, option); } /** * Checks if the function evaluates to 1. e.g. x^0 or 1 :) * @@param {bool} abs Compares the absolute value */ isOne(abs) { let f = abs ? 'absEquals' : 'equals'; if (this.group === Groups_1.Groups.N) return this.multiplier[f](1); else return this.power.equals(0); } isComposite() { let g = this.group, pg = this.previousGroup; return g === Groups_1.Groups.CP || g === Groups_1.Groups.PL || pg === Groups_1.Groups.PL || pg === Groups_1.Groups.CP; } isCombination() { let g = this.group, pg = this.previousGroup; return g === Groups_1.Groups.CB || pg === Groups_1.Groups.CB; } lessThan(n) { return this.multiplier.lessThan(n); } greaterThan(n) { if (!(0, Utils_1.isSymbol)(n)) { n = new Symbol(n); } // We can't tell for sure if a is greater than be if they're not both numbers if (!this.isConstant(true) || !n.isConstant(true)) {