UNPKG

nerdamer

Version:

javascript light-weight symbolic math expression evaluator

1,436 lines (1,308 loc) 679 kB
/* * Author : Martin Donk * Website : http://www.nerdamer.com * Email : martin.r.donk@gmail.com * Source : https://github.com/jiggzson/nerdamer */ /* global trig, trigh, Infinity, define, arguments2Array, NaN */ //externals ==================================================================== /* BigInterger.js v1.6.40 https://github.com/peterolson/BigInteger.js/blob/master/LICENSE */ //var nerdamerBigInt = typeof nerdamerBigInt !== 'undefined' ? nerdamerBigInt : require("big-integer"); /* big.js v5.2.2 https://github.com/MikeMcl/big.js/LICENCE */ //var nerdamerBigDecimal = typeof nerdamerBigDecimal !== 'undefined' ? nerdamerBigDecimal : require('big.js'); var nerdamer = (function (imports) { "use strict"; //version ====================================================================== var version = '1.1.13'; //inits ======================================================================== var _ = new Parser(); //nerdamer's parser //import bigInt var bigInt = imports.bigInt; var bigDec = imports.bigDec; //set the precision to js precision bigDec.set({ precision: 250 }); var Groups = {}; //container of pregenerated primes var PRIMES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113 , 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083]; //Settings ===================================================================== var CUSTOM_OPERATORS = {}; var Settings = { //Enables/Disables call peekers. False means callPeekers are disabled and true means callPeekers are enabled. callPeekers: false, //the max number up to which to cache primes. Making this too high causes performance issues init_primes: 1000, exclude: [], //If you don't care about division by zero for example then this can be set to true. //Has some nasty side effects so choose carefully. suppress_errors: false, //the global used to invoke the libary to parse to a number. Normally cos(9) for example returns //cos(9) for convenience but parse to number will always try to return a number if set to true. PARSE2NUMBER: false, //this flag forces the a clone to be returned when add, subtract, etc... is called SAFE: false, //the symbol to use for imaginary symbols IMAGINARY: 'i', //the modules used to link numeric function holders FUNCTION_MODULES: [Math], //Allow certain characters ALLOW_CHARS: ['π'], //Allow nerdamer to convert multi-character variables USE_MULTICHARACTER_VARS: true, //Allow changing of power operator POWER_OPERATOR: '^', //The variable validation regex //VALIDATION_REGEX: /^[a-z_][a-z\d\_]*$/i VALIDATION_REGEX: /^[a-z_αAβBγΓδΔϵEζZηHθΘιIκKλΛμMνNξΞoOπΠρPσΣτTυϒϕΦχXψΨωΩ∞][0-9a-z_αAβBγΓδΔϵEζZηHθΘιIκKλΛμMνNξΞoOπΠρPσΣτTυϒϕΦχXψΨωΩ]*$/i, // The regex used to determine which characters should be included in implied multiplication IMPLIED_MULTIPLICATION_REGEX: /([\+\-\/\*]*[0-9]+)([a-z_αAβBγΓδΔϵEζZηHθΘιIκKλΛμMνNξΞoOπΠρPσΣτTυϒϕΦχXψΨωΩ]+[\+\-\/\*]*)/gi, //Aliases ALIASES: { 'π': 'pi', '∞': 'Infinity' }, POSITIVE_MULTIPLIERS: false, //Cached items CACHE: {}, //Print out warnings or not SILENCE_WARNINGS: false, // Precision PRECISION: 21, // The Expression defaults to this value for decimal places EXPRESSION_DECP: 19, // The text function defaults to this value for decimal places DEFAULT_DECP: 16, //function mappings VECTOR: 'vector', PARENTHESIS: 'parens', SQRT: 'sqrt', ABS: 'abs', FACTORIAL: 'factorial', DOUBLEFACTORIAL: 'dfactorial', //reference pi and e LONG_PI: '3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214' + '808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196', LONG_E: '2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427427466' + '39193200305992181741359662904357290033429526059563073813232862794349076323382988075319525101901', PI: Math.PI, E: Math.E, LOG: 'log', LOG10: 'log10', LOG10_LATEX: 'log_{10}', MAX_EXP: 200000, //The number of scientific place to round to SCIENTIFIC_MAX_DECIMAL_PLACES: 14, //True if ints should not be converted to SCIENTIFIC_IGNORE_ZERO_EXPONENTS: true }; (function () { Settings.CACHE.roots = {}; var x = 40, y = 40; for(var i = 2; i <= x; i++) { for(var j = 2; j <= y; j++) { var nthpow = bigInt(i).pow(j); Settings.CACHE.roots[nthpow + '-' + j] = i; } } })(); //Add the groups. These have been reorganized as of v0.5.1 to make CP the highest group //The groups that help with organizing during parsing. Note that for FN is still a function even //when it's raised to a symbol, which typically results in an EX var N = Groups.N = 1, // A number P = Groups.P = 2, // A number with a rational power e.g. 2^(3/5). S = Groups.S = 3, // A single variable e.g. x. EX = Groups.EX = 4, // An exponential FN = Groups.FN = 5, // A function PL = Groups.PL = 6, // A symbol/expression having same name with different powers e.g. 1/x + x^2 CB = Groups.CB = 7, // A symbol/expression composed of one or more variables through multiplication e.g. x*y CP = Groups.CP = 8; // A symbol/expression composed of one variable and any other symbol or number x+1 or x+y var CONST_HASH = Settings.CONST_HASH = '#'; var PARENTHESIS = Settings.PARENTHESIS; var SQRT = Settings.SQRT; var ABS = Settings.ABS; var FACTORIAL = Settings.FACTORIAL; var DOUBLEFACTORIAL = Settings.DOUBLEFACTORIAL; //the storage container "memory" for parsed expressions var EXPRESSIONS = []; //variables var VARS = {}; //the container used to store all the reserved functions var RESERVED = []; var WARNINGS = []; /** * Use this when errors are suppressible * @param {String} msg * @param {object} ErrorObj */ var err = function (msg, ErrorObj) { if(!Settings.suppress_errors) { if(ErrorObj) throw new ErrorObj(msg); else throw new Error(msg); } }; //Utils ======================================================================== var customError = function (name) { var E = function (message) { this.name = name; this.message = message !== undefined ? message : ''; var error = new Error(this.message); error.name = this.name; this.stack = error.stack; }; //create an empty error E.prototype = Object.create(Error.prototype); return E; }; /** * Checks to see if value is one of nerdamer's reserved names * @param {String} value * @return boolean */ var isReserved = function (value) { return RESERVED.indexOf(value) !== -1; }; /** * Checks to see that all symbols in array are the same * @param {Symbol[]} arr * @returns {bool} */ var allSame = function (arr) { var last = arr[0]; for(var i = 1, l = arr.length; i < l; i++) if(!arr[i].equals(last)) return false; return true; }; /** * Used to pass warnings or low severity errors about the library * @param msg */ var warn = function (msg) { WARNINGS.push(msg); if(Settings.SHOW_WARNINGS && console && console.warn) { console.warn(msg); } }; /** * Enforces rule: "must start with a letter or underscore and * can have any number of underscores, letters, and numbers thereafter." * @param name The name of the symbol being checked * @param {String} typ - The type of symbols that's being validated * @throws {Exception} - Throws an exception on fail */ var validateName = function (name, typ) { typ = typ || 'variable'; if(Settings.ALLOW_CHARS.indexOf(name) !== -1) return; var regex = Settings.VALIDATION_REGEX; if(!(regex.test(name))) { throw new InvalidVariableNameError(name + ' is not a valid ' + typ + ' name'); } }; /** * Convert number from scientific format to decimal format * @param {Number} num */ var scientificToDecimal = function (num) { var nsign = Math.sign(num); //remove the sign num = Math.abs(num); //if the number is in scientific notation remove it if(/\d+\.?\d*e[\+\-]*\d+/i.test(num)) { var zero = '0', parts = String(num).toLowerCase().split('e'), //split into coeff and exponent e = parts.pop(), //store the exponential part l = Math.abs(e), //get the number of zeros sign = e / l, coeff_array = parts[0].split('.'); if(sign === -1) { l = l - coeff_array[0].length; if(l < 0) { num = coeff_array[0].slice(0, l) + '.' + coeff_array[0].slice(l) + (coeff_array.length === 2 ? coeff_array[1] : ''); } else { num = zero + '.' + new Array(l + 1).join(zero) + coeff_array.join(''); } } else { var dec = coeff_array[1]; if(dec) l = l - dec.length; if(l < 0) { num = coeff_array[0] + dec.slice(0, l) + '.' + dec.slice(l); } else { num = coeff_array.join('') + new Array(l + 1).join(zero); } } } return nsign < 0 ? '-' + num : num; }; /** * Checks if number is a prime number * @param {Number} n - the number to be checked */ var isPrime = function (n) { var q = Math.floor(Math.sqrt(n)); for(var i = 2; i <= q; i++) { if(n % i === 0) return false; } return true; }; /** * Generates an object with known variable value for evaluation * @param {String} variable * @param {any} value Any stringifyable object * @returns {Object} */ var knownVariable = function (variable, value) { var o = {}; o[variable] = value; return o; }; /** * Checks if n is a number * @param {any} n */ var isNumber = function (n) { return /^\d+\.?\d*$/.test(n); }; /** * Checks to see if an array contains only numeric values * @param {Array} arr */ var allNumeric = function (arr) { for(var i = 0; i < arr.length; i++) if(!isNumber(arr[i])) return false; return true; }; /** * Checks to see if a number or Symbol is a fraction * @param {Number|Symbol} num * @returns {boolean} */ var isFraction = function (num) { if(isSymbol(num)) return isFraction(num.multiplier.toDecimal()); return (num % 1 !== 0); }; /** * Checks to see if the object provided is a Symbol * @param {Object} obj */ var isSymbol = function (obj) { return (obj instanceof Symbol); }; /** * Checks to see if the object provided is an Expression * @param {Object} obj */ var isExpression = function (obj) { return (obj instanceof Expression); }; /** * This method traverses the symbol structure and grabs all the variables in a symbol. The variable * names are then returned in alphabetical order. * @param {Symbol} obj * @param {Boolean} poly * @param {Object} vars - An object containing the variables. Do not pass this in as it generated * automatically. In the future this will be a Collector object. * @returns {String[]} - An array containing variable names */ var variables = function (obj, poly, vars) { vars = vars || { c: [], add: function (value) { if(this.c.indexOf(value) === -1 && isNaN(value)) this.c.push(value); } }; if(isSymbol(obj)) { var group = obj.group, prevgroup = obj.previousGroup; if(group === EX) variables(obj.power, poly, vars); if(group === CP || group === CB || prevgroup === CP || prevgroup === CB) { for(var x in obj.symbols) { variables(obj.symbols[x], poly, vars); } } else if(group === S || prevgroup === S) { //very crude needs fixing. TODO if(!(obj.value === 'e' || obj.value === 'pi' || obj.value === Settings.IMAGINARY)) vars.add(obj.value); } else if(group === PL || prevgroup === PL) { variables(firstObject(obj.symbols), poly, vars); } else if(group === EX) { if(!isNaN(obj.value)) vars.add(obj.value); variables(obj.power, poly, vars); } else if(group === FN && !poly) { for(var i = 0; i < obj.args.length; i++) { variables(obj.args[i], poly, vars); } } } return vars.c.sort(); }; /** * Returns the sum of an array * @param {Array} arr * @param {boolean} toNumber * @returns {Symbol} */ var arraySum = function (arr, toNumber) { var sum = new Symbol(0); for(var i = 0; i < arr.length; i++) { var x = arr[i]; // Convert to symbol if not sum = _.add(sum, !isSymbol(x) ? _.parse(x) : x); } return toNumber ? Number(sum) : sum; }; /** * Separates out the variables into terms of variabls. * e.g. x+y+x*y+sqrt(2)+pi returns * {x: x, y: y, x y: x*y, constants: sqrt(2)+pi * @param {type} symbol * @param {type} o * @returns {undefined} * @throws {Error} for expontentials */ var separate = function (symbol, o) { symbol = _.expand(symbol); o = o || {}; var insert = function (key, sym) { if(!o[key]) o[key] = new Symbol(0); o[key] = _.add(o[key], sym.clone()); }; symbol.each(function (x) { if(x.isConstant('all')) { insert('constants', x); } else if(x.group === S) { insert(x.value, x); } else if(x.group === FN && (x.fname === ABS || x.fname === '')) { separate(x.args[0]); } else if(x.group === EX || x.group === FN) { throw new Error('Unable to separate. Term cannot be a function!'); } else { insert(variables(x).join(' '), x); } }); return o; }; /** * Fills holes in an array with zero symbol or generates one with n zeroes * @param {Array} arr * @param {Number} n */ var fillHoles = function (arr, n) { n = n || arr.length; for(var i = 0; i < n; i++) { var sym = arr[i]; if(!sym) arr[i] = new Symbol(0); } return arr; }; /** * * Checks to see if the object provided is a Vector * @param {Object} obj */ var isVector = function (obj) { return (obj instanceof Vector); }; /** * Checks to see if the object provided is a Matrix * @param {Object} obj */ var isMatrix = function (obj) { return (obj instanceof Matrix); }; var isSet = function (obj) { return (obj instanceof Set); }; /** * Checks to see if a symbol is in group N * @param {Symbol} symbol */ var isNumericSymbol = function (symbol) { return symbol.group === N || symbol.group === P; }; /** * Checks to see if a symbol is a variable with no multiplier nor power * @param {Symbol} symbol */ var isVariableSymbol = function (symbol) { return symbol.group === S && symbol.multiplier.equals(1) && symbol.power.equals(1); }; /** * Checks to see if the object provided is an Array * @param {Object} arr */ var isArray = function (arr) { return Array.isArray(arr); }; /** * Checks to see if a number is an integer * @param {Number} num */ var isInt = function (num) { return /^[-+]?\d+e?\+?\d*$/gim.test(num.toString()); }; /** * @param {Number|Symbol} obj * @returns {boolean} */ var isNegative = function (obj) { if(isSymbol(obj)) { obj = obj.multiplier; } return obj.lessThan(0); }; /** * Safely stringify object * @param o */ var stringify = function (o) { if(!o) return o; return String(o); }; /** * @param {String} str * @returns {String} - returns a formatted string surrounded by brackets */ var inBrackets = function (str) { return '(' + str + ')'; }; /** * A helper function to replace parts of string * @param {String} str - The original string * @param {Integer} from - The starting index * @param {Integer} to - The ending index * @param {String} with_str - The replacement string * @returns {String} - A formatted string */ var stringReplace = function (str, from, to, with_str) { return str.substr(0, from) + with_str + str.substr(to, str.length); }; /** * the Parser uses this to check if it's allowed to convert the obj to type Symbol * @param {Object} obj * @returns {boolean} */ var customType = function (obj) { return obj !== undefined && obj.custom; }; /** * Checks to see if numbers are both negative or are both positive * @param {Number} a * @param {Number} b * @returns {boolean} */ var sameSign = function (a, b) { return (a < 0) === (b < 0); }; /** * A helper function to replace multiple occurences in a string. Takes multiple arguments * @example format('{0} nice, {0} sweet', 'something') * //returns 'something nice, something sweet' */ var format = function () { var args = [].slice.call(arguments), str = args.shift(); var new_str = str.replace(/{(\d+)}/g, function (match, index) { var arg = args[index]; return typeof arg === 'function' ? arg() : arg; }); return new_str; }; /** * Generates an array with values within a range. Multiplies by a step if provided * @param {Number} start * @param {Number} end * @param {Number} step */ var range = function (start, end, step) { var arr = []; step = step || 1; for(var i = start; i <= end; i++) arr.push(i * step); return arr; }; /** * Returns an array of all the keys in an array * @param {Object} obj * @returns {Array} */ var keys = Object.keys; /** * Returns the first encountered item in an object. Items do not have a fixed order in objects * so only use if you need any first random or if there's only one item in the object * @param {Object} obj * @param {String} key Return this key as first object * @param {Boolean} both * @returns {*} */ var firstObject = function (obj, key, both) { for(var x in obj) break; if(key) return x; if(both) return { key: x, obj: obj[x] }; return obj[x]; }; /** * Substitutes out variables for two symbols, parses them to a number and them compares them numerically * @param {Symbol} sym1 * @param {Symbol} sym2 * @param {String[]} vars - an optional array of variables to use * @returns {bool} */ var compare = function (sym1, sym2, vars) { var n = 5; //a random number between 1 and 5 is good enough var scope = {}; // scope object with random numbers generated using vars var comparison; for(var i = 0; i < vars.length; i++) scope[vars[i]] = new Symbol(Math.floor(Math.random() * n) + 1); block('PARSE2NUMBER', function () { comparison = _.parse(sym1, scope).equals(_.parse(sym2, scope)); }); return comparison; }; /** * Is used to set a user defined function using the function assign operator * @param {String} name * @param {String[]} params_array * @param {String} body * @returns {Boolean} */ var setFunction = function (name, params_array, body) { validateName(name); if(!isReserved(name)) { params_array = params_array || variables(_.parse(body)); // The function gets set to PARSER.mapped function which is just // a generic function call. _.functions[name] = [_.mapped_function, params_array.length, { name: name, params: params_array, body: body }]; return body; } return null; }; /** * Returns the minimum number in an array * @param {Array} arr * @returns {Number} */ var arrayMax = function (arr) { return Math.max.apply(undefined, arr); }; /** * Returns the maximum number in an array * @param {Array} arr * @returns {Number} */ var arrayMin = function (arr) { return Math.min.apply(undefined, arr); }; /** * Checks to see if two arrays are equal * @param {Array} arr1 * @param {Array} arr2 */ var arrayEqual = function (arr1, arr2) { arr1.sort(); arr2.sort(); // The must be of the same length if(arr1.length === arr2.length) { for(var i = 0; i < arr1.length; i++) { // If any two items don't match we're done if(arr1[i] !== arr2[i]) { return false; } } // Otherwise they're equal return true; } return false; }; /** * Clones array with clonable items * @param {Array} arr * @returns {Array} */ var arrayClone = function (arr) { var new_array = [], l = arr.length; for(var i = 0; i < l; i++) new_array[i] = arr[i].clone(); return new_array; }; /** * Fills numbers between array values * @param {Numbers[]} arr * @param {Integer} slices */ var arrayAddSlices = function (arr, slices) { slices = slices || 20; var retval = []; var c, delta, e; retval.push(arr[0]); //push the beginning for(var i = 0; i < arr.length - 1; i++) { c = arr[i]; delta = arr[i + 1] - c; //get the difference e = delta / slices; //chop it up in the desired number of slices for(var j = 0; j < slices; j++) { c += e; //add the mesh to the last slice retval.push(c); } } return retval; }; /** * Gets nth roots of a number * @param {Symbol} symbol * @returns {Vector} */ var nroots = function (symbol) { var a, b; if(symbol.group === FN && symbol.fname === '') { a = Symbol.unwrapPARENS(_.parse(symbol).toLinear()); b = _.parse(symbol.power); } else if(symbol.group === P) { a = _.parse(symbol.value); b = _.parse(symbol.power); } if(a && b && (a.group === N) && b.group === N && a.multiplier.isNegative()) { var _roots = []; var parts = Symbol.toPolarFormArray(evaluate(symbol)); var r = parts[0]; //var r = _.parse(a).abs().toString(); //https://en.wikipedia.org/wiki/De_Moivre%27s_formula var x = _.arg(a); var n = b.multiplier.den.toString(); var p = b.multiplier.num.toString(); var formula = '(({0})^({1})*(cos({3})+({2})*sin({3})))^({4})'; for(var i = 0; i < n; i++) { var t = evaluate(_.parse(format("(({0})+2*pi*({1}))/({2})", x, i, n))).multiplier.toDecimal(); _roots.push(evaluate(_.parse(format(formula, r, n, Settings.IMAGINARY, t, p)))); } return Vector.fromArray(_roots); } else if(symbol.isConstant(true, true)) { var sign = symbol.sign(); var x = evaluate(symbol.abs()); var root = _.sqrt(x); var _roots = [root.clone(), root.negate()]; if(sign < 0) _roots = _roots.map(function (x) { return _.multiply(x, Symbol.imaginary()); }); } else { _roots = [_.parse(symbol)]; } return Vector.fromArray(_roots); }; /** * Sorts and array given 2 parameters * @param {String} a * @param {String} b */ var comboSort = function (a, b) { var l = a.length, combined = []; //the linker for(var i = 0; i < a.length; i++) { combined.push([a[i], b[i]]); //create the map } combined.sort(function (x, y) { return x[0] - y[0]; }); var na = [], nb = []; for(i = 0; i < l; i++) { na.push(combined[i][0]); nb.push(combined[i][1]); } return [na, nb]; }; /** * TODO: Pick a more descriptive name and better description * Breaks a function down into it's parts wrt to a variable, mainly coefficients * Example a*x^2+b wrt x * @param {Symbol} fn * @param {String} wrt * @param {bool} as_obj */ var decompose_fn = function (fn, wrt, as_obj) { wrt = String(wrt); //convert to string var ax, a, x, b; if(fn.group === CP) { var t = _.expand(fn.clone()).stripVar(wrt); ax = _.subtract(fn.clone(), t.clone()); b = t; } else ax = fn.clone(); a = ax.stripVar(wrt); x = _.divide(ax.clone(), a.clone()); b = b || new Symbol(0); if(as_obj) return { a: a, x: x, ax: ax, b: b }; return [a, x, ax, b]; }; /** * Rounds a number up to x decimal places * @param {Number} x * @param {Number} s */ var nround = function (x, s) { if(isInt(x)) { if(x >= Number.MAX_VALUE) return x.toString(); return Number(x); } s = typeof s === 'undefined' ? 14 : s; return Math.round(x * Math.pow(10, s)) / Math.pow(10, s); }; /** * Is used for u-substitution. Gets a suitable u for substitution. If for * instance a is used in the symbol then it keeps going down the line until * one is found that's not in use. If all letters are taken then it * starts appending numbers. * IMPORTANT! It assumes that the substitution will be undone * beore the user gets to interact with the object again. * @param {Symbol} symbol */ var getU = function (symbol) { //start with u var u = 'u', //start with u v = u, //init with u c = 0, //postfix number vars = variables(symbol); //make sure this variable isn't reserved and isn't in the variable list while(!(RESERVED.indexOf(v) === - 1 && vars.indexOf(v) === - 1)) v = u + c++; //get an empty slot. It seems easier to just push but the //problem is that we may have some which are created by clearU for(var i = 0, l = RESERVED.length; i <= l; i++) //reserved cannot equals false or 0 so we can safely check for a falsy type if(!RESERVED[i]) { RESERVED[i] = v; //reserve the variable break; } return v; }; /** * Clears the u variable so it's no longer reserved * @param {String} u */ var clearU = function (u) { var indx = RESERVED.indexOf(u); if(indx !== -1) RESERVED[indx] = undefined; }; /** * Loops through each item in object and calls function with item as param * @param {Object|Array} obj * @param {Function} fn */ var each = function (obj, fn) { if(isArray(obj)) { var l = obj.length; for(var i = 0; i < l; i++) fn.call(obj, i); } else { for(var x in obj) if(obj.hasOwnProperty(x)) fn.call(obj, x); } }; /** * Checks to see if a number is an even number * @param {Number} num * @returns {boolean} */ var even = function (num) { return num % 2 === 0; }; /** * Checks to see if a fraction is divisible by 2 * @param {Number} num * @returns {boolean} */ var evenFraction = function (num) { return 1 / (num % 1) % 2 === 0; }; /** * Strips duplicates out of an array * @param {Array} arr */ var arrayUnique = function (arr) { var l = arr.length, a = []; for(var i = 0; i < l; i++) { var item = arr[i]; if(a.indexOf(item) === -1) a.push(item); } return a; }; /** * Gets all the variables in an array of Symbols * @param {Symbol[]} arr */ var arrayGetVariables = function (arr) { var vars = variables(arr[0], null, null, true); //get all variables for(var i = 1, l = arr.length; i < l; i++) vars = vars.concat(variables(arr[i])); //remove duplicates vars = arrayUnique(vars).sort(); //done return vars; }; /** * Removes duplicates from an array. Returns a new array * @param {Array} arr * @param {Function} condition */ var removeDuplicates = function (arr, condition) { var conditionType = typeof condition; if(conditionType !== 'function' || conditionType === 'undefined') { condition = function (a, b) { return a === b; }; } var seen = []; while(arr.length) { var a = arr[0]; //only one element left so we're done if(arr.length === 1) { seen.push(a); break; } var temp = []; seen.push(a); //we already scanned these for(var i = 1; i < arr.length; i++) { var b = arr[i]; //if the number is outside the specified tolerance if(!condition(a, b)) temp.push(b); } //start over with the remainder arr = temp; } return seen; }; /** * Reserves the names in an object so they cannot be used as function names * @param {Object} obj */ var reserveNames = function (obj) { var add = function (item) { if(RESERVED.indexOf(item) === -1) RESERVED.push(item); }; if(typeof obj === 'string') add(obj); else { each(obj, function (x) { add(x); }); } }; /** * Removes an item from either an array or an object. If the object is an array, the index must be * specified after the array. If it's an object then the key must be specified * @param {Object|Array} obj * @param {Integer} indexOrKey */ var remove = function (obj, indexOrKey) { var result; if(isArray(obj)) { result = obj.splice(indexOrKey, 1)[0]; } else { result = obj[indexOrKey]; delete obj[indexOrKey]; } return result; }; /** * Creates a temporary block in which one of the global settings is temporarily modified while * the function is called. For instance if you want to parse directly to a number rather than have a symbolic * answer for a period you would set PARSE2NUMBER to true in the block. * @example block('PARSE2NUMBER', function(){//symbol being parsed to number}, true); * @param {String} setting - The setting being accessed * @param {Function} f * @param {boolean} opt - The value of the setting in the block * @param {String} obj - The obj of interest. Usually a Symbol but could be any object */ var block = function (setting, f, opt, obj) { var current_setting = Settings[setting]; Settings[setting] = opt === undefined ? true : !!opt; var retval = f.call(obj); Settings[setting] = current_setting; return retval; }; /** * provide a mechanism for accessing functions directly. Not yet complete!!! * Some functions will return undefined. This can maybe just remove the * function object at some point when all functions are eventually * housed in the global function object. Returns ALL parser available * functions. Parser.functions may not contain all functions */ var importFunctions = function () { var o = {}; for(var x in _.functions) o[x] = _.functions[x][0]; return o; }; /** * Converts function arguments to an array. Now used by gcd and lcm in Algebra.js :) * @param {Array|object} obj */ var arguments2Array = function (obj) { return [].slice.call(obj); }; /** * Returns the coefficients of a symbol given a variable. Given ax^2+b^x+c, it divides * each nth term by x^n. * @param {Symbol} symbol * @param {Symbol} wrt */ var getCoeffs = function (symbol, wrt, info) { var coeffs = []; //we loop through the symbols and stick them in their respective //containers e.g. y*x^2 goes to index 2 symbol.each(function (term) { if(term.contains(wrt)) { //we want only the coefficient which in this case will be everything but the variable //e.g. a*b*x -> a*b if the variable to solve for is x var coeff = term.stripVar(wrt), x = _.divide(term.clone(), coeff.clone()), p = x.power.toDecimal(); } else { coeff = term; p = 0; } var e = coeffs[p]; //if it exists just add it to it coeffs[p] = e ? _.add(e, coeff) : coeff; }, true); for(var i = 0; i < coeffs.length; i++) if(!coeffs[i]) coeffs[i] = new Symbol(0); //fill the holes return coeffs; }; /** * As the name states. It forces evaluation of the expression * @param {Symbol} symbol * @param {Symbol} o */ var evaluate = function (symbol, o) { return block('PARSE2NUMBER', function () { return _.parse(symbol, o); }, true); }; /** * Converts an array to a vector. Consider moving this to Vector.fromArray * @param {String[]|String|Symbol|Number|Number[]} x */ var convertToVector = function (x) { if(isArray(x)) { var vector = new Vector([]); for(var i = 0; i < x.length; i++) vector.elements.push(convertToVector(x[i])); return vector; } //Ensure that a nerdamer ready object is returned if(!isSymbol(x)) return _.parse(x); return x; }; /** * Generates prime numbers up to a specified number * @param {Number} upto */ var generatePrimes = function (upto) { //get the last prime in the array var last_prime = PRIMES[PRIMES.length - 1] || 2; //no need to check if we've already encountered the number. Just check the cache. for(var i = last_prime; i < upto; i++) { if(isPrime(i)) PRIMES.push(i); } }; /** * Checks to see if all arguments are numbers * @param {object} args */ var allNumbers = function (args) { for(var i = 0; i < args.length; i++) if(args[i].group !== N) return false; return true; }; /* * Checks if all arguments aren't just all number but if they * are constants as well e.g. pi, e. * @param {object} args */ var allConstants = function (args) { for(var i = 0; i < args.length; i++) { if(args[i].isPi() || args[i].isE()) continue; if(!args[i].isConstant(true)) return false; } return true; }; /** * Used to multiply two expression in expanded form * @param {Symbol} a * @param {Symbol} b */ var mix = function (a, b, opt) { // Flip them if b is a CP or PL and a is not if(b.isComposite() && !a.isComposite() || b.isLinear() && !a.isLinear()) { [a, b] = [b, a]; } // A temporary variable to hold the expanded terms var t = new Symbol(0); if(a.isLinear()) { a.each(function (x) { // If b is not a PL or a CP then simply multiply it if(!b.isComposite()) { var term = _.multiply(_.parse(x), _.parse(b)); t = _.add(t, _.expand(term, opt)); } // Otherwise multiply out each term. else if(b.isLinear()) { b.each(function (y) { var term = _.multiply(_.parse(x), _.parse(y)); var expanded = _.expand(_.parse(term), opt); t = _.add(t, expanded); }, true); } else { t = _.add(t, _.multiply(x, _.parse(b))); } }, true); } else { // Just multiply them together t = _.multiply(a, b); } // The expanded function is now t return t; }; //Exceptions =================================================================== //Is thrown for division by zero var DivisionByZero = customError('DivisionByZero'); // Is throw if an error occured during parsing var ParseError = customError('ParseError'); // Is thrown if the expression results in undefined var UndefinedError = customError('UndefinedError'); // Is throw input is out of the function domain var OutOfFunctionDomainError = customError('OutOfFunctionDomainError'); // Is throw if a function exceeds x amount of iterations var MaximumIterationsReached = customError('MaximumIterationsReached'); // Is thrown if the parser receives an incorrect type var NerdamerTypeError = customError('NerdamerTypeError'); // Is thrown if bracket parity is not correct var ParityError = customError('ParityError'); // Is thrown if an unexpectd or incorrect operator is encountered var OperatorError = customError('OperatorError'); // Is thrown if an index is out of range. var OutOfRangeError = customError('OutOfRangeError'); // Is thrown if dimensions are incorrect. Mostly for matrices var DimensionError = customError('DimensionError'); // Is thrown if variable name violates naming rule var InvalidVariableNameError = customError('InvalidVariableNameError'); // Is thrown if the limits of the library are exceeded for a function // This can be that the function become unstable passed a value var ValueLimitExceededError = customError('ValueLimitExceededError'); // Is throw if the value is an incorrect LH or RH value var NerdamerValueError = customError('NerdamerValueError'); // Is thrown if the value is an incorrect LH or RH value var SolveError = customError('SolveError'); // Is thrown for an infinite loop var InfiniteLoopError = customError('InfiniteLoopError'); // Is thrown if an operator is found when there shouldn't be one var UnexpectedTokenError = customError('UnexpectedTokenError'); var exceptions = { DivisionByZero: DivisionByZero, ParseError: ParseError, OutOfFunctionDomainError: OutOfFunctionDomainError, UndefinedError: UndefinedError, MaximumIterationsReached: MaximumIterationsReached, NerdamerTypeError: NerdamerTypeError, ParityError: ParityError, OperatorError: OperatorError, OutOfRangeError: OutOfRangeError, DimensionError: DimensionError, InvalidVariableNameError: InvalidVariableNameError, ValueLimitExceededError: ValueLimitExceededError, NerdamerValueError: NerdamerValueError, SolveError: SolveError, InfiniteLoopError: InfiniteLoopError, UnexpectedTokenError: UnexpectedTokenError }; //Math2 ======================================================================== //This object holds additional functions for nerdamer. Think of it as an extension of the Math object. //I really don't like touching objects which aren't mine hence the reason for Math2. The names of the //functions within are pretty self-explanatory. //NOTE: DO NOT USE INLINE COMMENTS WITH THE MATH2 OBJECT! THIS BREAK DURING COMPILATION OF BUILDFUNCTION. var Math2 = { csc: function (x) { return 1 / Math.sin(x); }, sec: function (x) { return 1 / Math.cos(x); }, cot: function (x) { return 1 / Math.tan(x); }, acsc: function (x) { return Math.asin(1 / x); }, asec: function (x) { return Math.acos(1 / x); }, acot: function (x) { return (Math.PI / 2) - Math.atan(x); }, // https://gist.github.com/jiggzson/df0e9ae8b3b06ff3d8dc2aa062853bd8 erf: function (x) { var t = 1 / (1 + 0.5 * Math.abs(x)); var result = 1 - t * Math.exp(-x * x - 1.26551223 + t * (1.00002368 + t * (0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * (0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * (-0.82215223 + t * (0.17087277))))))))) ); return x >= 0 ? result : -result; }, diff: function (f) { var h = 0.001; var derivative = function (x) { return (f(x + h) - f(x - h)) / (2 * h); }; return derivative; }, median: function (...values) { values.sort(function (a, b) { return a - b; }); var half = Math.floor(values.length / 2); if(values.length % 2) return values[half]; return (values[half - 1] + values[half]) / 2.0; }, /* * Reverses continued fraction calculation * @param {obj} contd * @returns {Number} */ fromContinued: function (contd) { var arr = contd.fractions.slice(); var e = 1 / arr.pop(); for(var i = 0, l = arr.length; i < l; i++) { e = 1 / (arr.pop() + e); } return contd.sign * (contd.whole + e); }, /* * Calculates continued fractions * @param {Number} n * @param {Number} x The number of places * @returns {Number} */ continuedFraction: function (n, x) { x = x || 20; var sign = Math.sign(n); /*store the sign*/ var absn = Math.abs(n); /*get the absolute value of the number*/ var whole = Math.floor(absn); /*get the whole*/ var ni = absn - whole; /*subtract the whole*/ var c = 0; /*the counter to keep track of iterations*/ var done = false; var epsilon = 1e-14; var max = 1e7; var e, w; var retval = { whole: whole, sign: sign, fractions: [] }; /*start calculating*/ while(!done && ni !== 0) { /*invert and get the whole*/ e = 1 / ni; w = Math.floor(e); if(w > max) { /*this signals that we may have already gone too far*/ var d = Math2.fromContinued(retval) - n; if(d <= Number.EPSILON) break; } /*add to result*/ retval.fractions.push(w); /*move the ni to the decimal*/ ni = e - w; /*ni should always be a decimal. If we have a whole number then we're in the rounding errors*/ if(ni <= epsilon || c >= x - 1) done = true; c++; } /*cleanup 1/(n+1/1) = 1/(n+1) so just move the last digit one over if it's one*/ var idx = retval.fractions.length - 1; if(retval.fractions[idx] === 1) { retval.fractions.pop();