nerdamer-prime
Version:
javascript light-weight symbolic math library
1,522 lines (1,382 loc) • 761 kB
JavaScript
/*
* 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.16';
//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,
];
var PRIMES_SET = {};
for (const p of PRIMES) {
PRIMES_SET[p] = true;
}
//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: '^',
// Function catch regex
FUNCTION_REGEX: /^\s*([a-z_][a-z0-9_]*)\(([a-z0-9_,\s]*)\)\s*:?=\s*(.+)\s*$/i,
//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}',
LOG2: 'log2',
LOG2_LATEX: 'log_{2}',
LOG1P: 'log1p',
LOG1P_LATEX: 'ln\\left( 1 + {0} \\right)',
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,
// no simplify() or solveFor() should take more ms than this
TIMEOUT: 800,
};
(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;
}
}
})();
var __starttime = 0;
var __timeout;
function armTimeout() {
if (console.global && console.global.tsDebugChannels && console.global.tsDebugChannels['notimeout']) {
disarmTimeout();
return;
}
__starttime = Date.now();
__timeout = Settings.TIMEOUT;
}
function disarmTimeout() {
__starttime = 0;
}
function checkTimeout() {
if (__starttime !== 0 && Date.now() > __starttime + __timeout) {
throw new Error('timeout');
}
}
/**
* Class used to collect arguments for functions
*
* @returns {Parser.Collection}
*/
function Collection() {
this.elements = [];
}
Collection.prototype.append = function (e) {
this.elements.push(e);
};
Collection.prototype.getItems = function () {
return this.elements;
};
Collection.prototype.toString = function () {
return _.pretty_print(this.elements);
};
Collection.prototype.dimensions = function () {
return this.elements.length;
};
Collection.prototype.text = function (options) {
return '(' + this.elements.map(e => e.text(options)).join(',') + ')';
};
Collection.create = function (e) {
var collection = new Collection();
if (e) collection.append(e);
return collection;
};
Collection.prototype.clone = function (elements) {
const c = Collection.create();
c.elements = this.elements.map(e => e.clone());
return c;
};
Collection.prototype.expand = function (options) {
this.elements = this.elements.map(e => _.expand(e, options));
return this;
};
Collection.prototype.evaluate = function () {
this.elements = this.elements.map(e => _.evaluate(e, options));
return this;
};
Collection.prototype.map = function (lambda) {
const c2 = this.clone();
c2.elements = c2.elements.map((x, i) => lambda(x, i + 1));
return c2;
};
// Returns the result of adding the argument to the vector
Collection.prototype.add = function (c2) {
return block(
'SAFE',
function () {
var V = c2.elements || c2;
if (this.elements.length !== V.length) {
return null;
}
return this.map(function (x, i) {
return _.add(x, V[i - 1]);
});
},
undefined,
this
);
};
// Returns the result of subtracting the argument from the vector
Collection.prototype.subtract = function (vector) {
return block(
'SAFE',
function () {
var V = vector.elements || vector;
if (this.elements.length !== V.length) {
return null;
}
return this.map(function (x, i) {
return _.subtract(x, V[i - 1]);
});
},
undefined,
this
);
};
//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
var P = (Groups.P = 2); // A number with a rational power e.g. 2^(3/5).
var S = (Groups.S = 3); // A single variable e.g. x.
var EX = (Groups.EX = 4); // An exponential
var FN = (Groups.FN = 5); // A function
var PL = (Groups.PL = 6); // A symbol/expression having same name with different powers e.g. 1/x + x^2
var CB = (Groups.CB = 7); // A symbol/expression composed of one or more variables through multiplication e.g. x*y
var 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 = [];
var USER_FUNCTIONS = [];
/**
* 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
* @returns 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 = Math.sign(e),
coeff_array = parts[0].split('.');
if (sign === -1) {
//return "("+parts[0]+"/1"+"0".repeat(l)+")";
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) {
if (n in PRIMES_SET) {
return true;
}
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 exponentials
*/
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) {
// todo: gm: this occurs with sqrt(a+1)
return null;
} 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;
};
var isCollection = function (obj) {
return obj instanceof Collection;
};
/**
* 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) {
if (typeof num === 'number') {
return Number.isInteger(num);
}
return typeof num !== 'undefined' && /^[-+]?\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 {any}
*/
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 and also is used to set a user defined
* JavaScript function using the function assign operator
*
* @param {string | Function} fnName
* @param {string[]} [fnParams]
* @param {string} [fnBody]
* @returns {boolean}
*/
var setFunction = function (fnName, fnParams, fnBody) {
if (!fnParams) {
var fnNameType = typeof fnName;
// Option setFunction('f(x)=x^2+2'), setFunction('f(x):=x^2+2')
if (fnNameType === 'string') {
if (!/:?=/.test(fnName)) {
return false;
}
var match = Settings.FUNCTION_REGEX.exec(fnName);
if (!match) {
return false;
}
var [, fName, fParams, fBody] = match;
fnName = fName;
fnParams = fParams.split(',').map(arg => arg.trim());
fnBody = fBody;
}
// Option setFunction(function fox(x) { return x^2; })
else if (fnNameType === 'function') {
var jsFunction = fnName;
var jsName = jsFunction.name;
validateName(jsName);
if (!isReserved(jsName)) {
C.Math2[jsName] = jsFunction;
_.functions[jsName] = [, jsFunction.length];
if (!USER_FUNCTIONS.includes(jsName)) {
USER_FUNCTIONS.push(jsName);
}
return true;
}
return false;
} else {
return false;
}
}
fnName = fnName.trim();
validateName(fnName);
// Option setFunction('f(x)', ['x'], 'x^2+2') or setFunction('f(x)=x^2+2'), setFunction('f(x):=x^2+2')
if (!isReserved(fnName)) {
fnParams = fnParams || variables(_.parse(fnBody));
fnParams = fnParams.map(p => p.trim());
// The function gets set to PARSER.mapped function which is just
// a generic function call.
_.functions[fnName] = [
_.mapped_function,
fnParams.length,
{
name: fnName,
params: fnParams,
body: fnBody,
},
];
if (!USER_FUNCTIONS.includes(fnName)) {
USER_FUNCTIONS.push(fnName);
}
return true;
}
return false;
};
/** Clears all user defined functions */
var clearFunctions = function () {
for (var name of USER_FUNCTIONS) {
delete C.Math2[name];
delete _.functions[name];
}
};
/**
* 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);
PRIMES_SET[i] = true;
}
};
/**
* 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