diff --git a/AUTHORS b/AUTHORS index 883c06e118..93895801fb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -194,5 +194,7 @@ Shadman Kolahzary Laureen Beaudry <63020359+laureen-m@users.noreply.github.com> BOUYA Med. Salim Alexander +FelixSelter <55546882+FelixSelter@users.noreply.github.com> +Hansuku <1556207795@qq.com> # Generated by tools/update-authors.js diff --git a/README.md b/README.md index a9884df104..4e9ab9e61b 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ See the [Getting Started](https://mathjs.org/docs/getting_started.html) for a mo ## Browser support -Math.js works on any ES5 compatible JavaScript engine: node.js, Chrome, Firefox, Safari, Edge, and IE11. +Math.js works on any ES6 compatible JavaScript engine, including node.js, Chrome, Firefox, Safari, and Edge. ## Documentation diff --git a/docs/core/chaining.md b/docs/core/chaining.md index c97bcc57d9..a26d36755d 100644 --- a/docs/core/chaining.md +++ b/docs/core/chaining.md @@ -39,3 +39,11 @@ a number of special functions: Executes `math.format(value)` onto the chain's value, returning a string representation of the value. +Note that a "rest" or "..." parameter may not be broken across the value +in the chain and a function call. For example + +```js +math.chain(3).median(4,5).done() // throws error +``` + +does not compute the median of 3, 4, and 5. diff --git a/docs/datatypes/units.md b/docs/datatypes/units.md index 1d7cf86bf7..1205be857d 100644 --- a/docs/datatypes/units.md +++ b/docs/datatypes/units.md @@ -250,6 +250,10 @@ Returns a clone of a unit represented in SI units. Works with units with or with Get a string representation of the unit. The function will determine the best fitting prefix for the unit. +### unit.valType() +Get the string name of the current type of the value of this Unit object, e.g. +'number', 'BigNumber', etc. + ## Unit reference This section lists all available units, prefixes, and physical constants. These can be used via the Unit object, or via `math.evaluate()`. diff --git a/docs/expressions/algebra.md b/docs/expressions/algebra.md index 1f851afbb1..ec506b9591 100644 --- a/docs/expressions/algebra.md +++ b/docs/expressions/algebra.md @@ -27,6 +27,12 @@ console.log(simplified.toString()) // '3 * x' console.log(simplified.evaluate({x: 4})) // 12 ``` +Among its other actions, calling `simplify()` on an expression will convert +any functions that have operator equivalents to their operator form: +```js +console.log(math.simplify('multiply(x,3)').toString) // '3 * x' +``` + Note that `simplify` has an optional argument `scope` that allows the definitions of variables in the expression (as numeric values, or as further expressions) to be specified and used in the simplification, e.g. continuing the previous example, ```js diff --git a/docs/expressions/syntax.md b/docs/expressions/syntax.md index 4fb985bc60..6b4db1be20 100644 --- a/docs/expressions/syntax.md +++ b/docs/expressions/syntax.md @@ -47,7 +47,11 @@ math.evaluate('2 + 3 * 4') // 14 math.evaluate('(2 + 3) * 4') // 20 ``` -The following operators are available: +The following operators are available. Note that almost every operator listed +also has a function form with identical meaning that can be used +interchangeably. For example, `x+y` will always evaluate identically to +`add(x,y)`. For a full list of the equivalences, see the section on +Functions below. Operator | Name | Syntax | Associativity | Example | Result ----------- | -------------------------- | ---------- | ------------- | --------------------- | --------------- @@ -197,6 +201,46 @@ const parser = math.parser() parser.evaluate('f = typed({"number": f(x) = x ^ 2 - 5})') ``` +Finally, as mentioned above, there is a function form for nearly every one of +the mathematical operator symbols. Moreover, for some associative operators, +the corresponding function allows arbitrarily many arguments. The table below +gives the full correspondence. + +Operator Expression | Equivalent Function Expression +---------------------|------------------------------- +`a or b` |`or(a,b)` +`a xor b` |`xor(a,b)` +`a and b` |`and(a,b)` +`a \| b` |`bitOr(a,b)` +`a ^\| b` |`bitXor(a,b)` +`a & b` |`bitAnd(a,b)` +`a == b` |`equal(a,b)` +`a != b` |`unequal(a,b)` +`a < b` |`smaller(a,b)` +`a > b` |`larger(a,b)` +`a <= b` |`smallerEq(a,b)` +`a << 3` |`leftShift(a,3)` +`a >> 3` |`rightArithShift(a,3)` +`a >>> 3` |`rightLogShift(a,3)` +`u to cm` |`to(u, cm)` +`a + b + c + ...` |`add(a,b,c,...)` +`a - b` |`subtract(a,b)` +`a * b * c * ...` |`multiply(a,b,c,...)` +`A .* B` |`dotMultiply(A,B)` +`A ./ B` |`dotDivide(A,B)` +`a mod b` |`mod(a,b)` +`+a` |`unaryPlus(a)` +`-a` |`unaryMinus(a)` +`~a` |`bitNot(a)` +`not a` |`not(a)` +`a^b` |`pow(a,b)` +`A .^ B` |`dotPow(A,B)` +`a!` |`factorial(a)` +`A'` |`ctranspose(A)` + +Note that math.js embodies a preference for the operator forms, in that calling +`simplify` (see [Algebra](./algebra.md)) converts any instances of the function +form into the corresponding operator. ## Constants and variables diff --git a/package-lock.json b/package-lock.json index 4f93ce1f2a..7e65050e7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "javascript-natural-sort": "^0.7.1", "seedrandom": "^3.0.5", "tiny-emitter": "^2.1.0", - "typed-function": "^2.1.0" + "typed-function": "^3.0.0" }, "bin": { "mathjs": "bin/cli.js" @@ -15542,11 +15542,11 @@ } }, "node_modules/typed-function": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.1.0.tgz", - "integrity": "sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-3.0.0.tgz", + "integrity": "sha512-mKJKkt2xYxJUuMD7jyfgUxfn5KCsCxkEKBVjep5yYellJJ5aEDO2QUAmIGdvcZmfQnIrplkzELIaG+5b1475qg==", "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/typedarray": { @@ -28867,9 +28867,9 @@ } }, "typed-function": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.1.0.tgz", - "integrity": "sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-3.0.0.tgz", + "integrity": "sha512-mKJKkt2xYxJUuMD7jyfgUxfn5KCsCxkEKBVjep5yYellJJ5aEDO2QUAmIGdvcZmfQnIrplkzELIaG+5b1475qg==" }, "typedarray": { "version": "0.0.6", diff --git a/package.json b/package.json index f6381e4673..ca6a4cdf1c 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "javascript-natural-sort": "^0.7.1", "seedrandom": "^3.0.5", "tiny-emitter": "^2.1.0", - "typed-function": "^2.1.0" + "typed-function": "^3.0.0" }, "devDependencies": { "@babel/core": "7.18.6", @@ -176,8 +176,5 @@ "bugs": { "url": "https://github.com/josdejong/mathjs/issues" }, - "browserslist": [ - "IE 11" - ], "sideEffects": false } diff --git a/src/core/create.js b/src/core/create.js index dcb416fb0f..88c4556b56 100644 --- a/src/core/create.js +++ b/src/core/create.js @@ -1,4 +1,5 @@ import './../utils/polyfills.js' +import typedFunction from 'typed-function' import { deepFlatten, isLegacyFactory, values } from '../utils/object.js' import * as emitter from './../utils/emitter.js' import { importFactory } from './function/import.js' @@ -206,6 +207,8 @@ export function create (factories, config) { function lazyTyped (...args) { return math.typed.apply(math.typed, args) } + lazyTyped.isTypedFunction = typedFunction.isTypedFunction + const internalImport = importFactory(lazyTyped, load, math, importedFactories) math.import = internalImport diff --git a/src/core/function/import.js b/src/core/function/import.js index b4a6a02b05..ac7c5980cc 100644 --- a/src/core/function/import.js +++ b/src/core/function/import.js @@ -144,7 +144,7 @@ export function importFactory (typed, load, math, importedFactories) { }) } - if (isTypedFunction(math[name]) && isTypedFunction(value)) { + if (typed.isTypedFunction(math[name]) && typed.isTypedFunction(value)) { if (options.override) { // give the typed function the right name value = typed(name, value.signatures) @@ -280,7 +280,7 @@ export function importFactory (typed, load, math, importedFactories) { return instance } - if (isTypedFunction(existing) && isTypedFunction(instance)) { + if (typed.isTypedFunction(existing) && typed.isTypedFunction(instance)) { // merge the existing and new typed function return typed(existing, instance) } @@ -344,15 +344,6 @@ export function importFactory (typed, load, math, importedFactories) { Array.isArray(object) } - /** - * Test whether a given thing is a typed-function - * @param {*} fn - * @return {boolean} Returns true when `fn` is a typed-function - */ - function isTypedFunction (fn) { - return typeof fn === 'function' && typeof fn.signatures === 'object' - } - function hasTypedFunctionSignature (fn) { return typeof fn === 'function' && typeof fn.signature === 'string' } diff --git a/src/core/function/typed.js b/src/core/function/typed.js index ad51b4a643..037345cfc8 100644 --- a/src/core/function/typed.js +++ b/src/core/function/typed.js @@ -45,6 +45,7 @@ import { isBlockNode, isBoolean, isChain, + isCollection, isComplex, isConditionalNode, isConstantNode, @@ -109,12 +110,21 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi // define all types. The order of the types determines in which order function // arguments are type-checked (so for performance it's important to put the // most used types first). - typed.types = [ + typed.clear() + typed.addTypes([ { name: 'number', test: isNumber }, { name: 'Complex', test: isComplex }, { name: 'BigNumber', test: isBigNumber }, { name: 'Fraction', test: isFraction }, { name: 'Unit', test: isUnit }, + // The following type matches a valid variable name, i.e., an alphanumeric + // string starting with an alphabetic character. It is used (at least) + // in the definition of the derivative() function, as the argument telling + // what to differentiate over must (currently) be a variable. + { + name: 'identifier', + test: s => isString && /^\p{L}[\p{L}\d]*$/u.test(s) + }, { name: 'string', test: isString }, { name: 'Chain', test: isChain }, { name: 'Array', test: isArray }, @@ -150,9 +160,9 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi { name: 'Map', test: isMap }, { name: 'Object', test: isObject } // order 'Object' last, it matches on other classes too - ] + ]) - typed.conversions = [ + typed.addConversions([ { from: 'number', to: 'BigNumber', @@ -179,12 +189,6 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi return new Complex(x, 0) } - }, { - from: 'number', - to: 'string', - convert: function (x) { - return x + '' - } }, { from: 'BigNumber', to: 'Complex', @@ -336,7 +340,45 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi return matrix.valueOf() } } - ] + ]) + + // Provide a suggestion on how to call a function elementwise + // This was added primarily as guidance for the v10 -> v11 transition, + // and could potentially be removed in the future if it no longer seems + // to be helpful. + typed.onMismatch = (name, args, signatures) => { + const usualError = typed.createError(name, args, signatures) + if (['wrongType', 'mismatch'].includes(usualError.data.category) && + args.length === 1 && isCollection(args[0]) && + // check if the function can be unary: + signatures.some(sig => !sig.params.includes(','))) { + const err = new TypeError( + `Function '${name}' doesn't apply to matrices. To call it ` + + `elementwise on a matrix 'M', try 'map(M, ${name})'.`) + err.data = usualError.data + throw err + } + throw usualError + } + + // Provide a suggestion on how to call a function elementwise + // This was added primarily as guidance for the v10 -> v11 transition, + // and could potentially be removed in the future if it no longer seems + // to be helpful. + typed.onMismatch = (name, args, signatures) => { + const usualError = typed.createError(name, args, signatures) + if (['wrongType', 'mismatch'].includes(usualError.data.category) && + args.length === 1 && isCollection(args[0]) && + // check if the function can be unary: + signatures.some(sig => !sig.params.includes(','))) { + const err = new TypeError( + `Function '${name}' doesn't apply to matrices. To call it ` + + `elementwise on a matrix 'M', try 'map(M, ${name})'.`) + err.data = usualError.data + throw err + } + throw usualError + } return typed }) diff --git a/src/expression/embeddedDocs/embeddedDocs.js b/src/expression/embeddedDocs/embeddedDocs.js index 34ff8092d2..19779e38f5 100644 --- a/src/expression/embeddedDocs/embeddedDocs.js +++ b/src/expression/embeddedDocs/embeddedDocs.js @@ -40,6 +40,7 @@ import { qrDocs } from './function/algebra/qr.js' import { rationalizeDocs } from './function/algebra/rationalize.js' import { resolveDocs } from './function/algebra/resolve.js' import { simplifyDocs } from './function/algebra/simplify.js' +import { simplifyConstantDocs } from './function/algebra/simplifyConstant.js' import { simplifyCoreDocs } from './function/algebra/simplifyCore.js' import { sluDocs } from './function/algebra/slu.js' import { symbolicEqualDocs } from './function/algebra/symbolicEqual.js' @@ -338,6 +339,7 @@ export const embeddedDocs = { leafCount: leafCountDocs, resolve: resolveDocs, simplify: simplifyDocs, + simplifyConstant: simplifyConstantDocs, simplifyCore: simplifyCoreDocs, symbolicEqual: symbolicEqualDocs, rationalize: rationalizeDocs, diff --git a/src/expression/embeddedDocs/function/algebra/simplifyConstant.js b/src/expression/embeddedDocs/function/algebra/simplifyConstant.js new file mode 100644 index 0000000000..f86645f969 --- /dev/null +++ b/src/expression/embeddedDocs/function/algebra/simplifyConstant.js @@ -0,0 +1,16 @@ +export const simplifyConstantDocs = { + name: 'simplifyConstant', + category: 'Algebra', + syntax: [ + 'simplifyConstant(expr)', + 'simplifyConstant(expr, options)' + ], + description: 'Replace constant subexpressions of node with their values.', + examples: [ + 'simplifyConatant("(3-3)*x")', + 'simplifyConstant(parse("z-cos(tau/8)"))' + ], + seealso: [ + 'simplify', 'simplifyCore', 'evaluate' + ] +} diff --git a/src/expression/embeddedDocs/function/algebra/simplifyCore.js b/src/expression/embeddedDocs/function/algebra/simplifyCore.js index 6d967847f7..9a12202675 100644 --- a/src/expression/embeddedDocs/function/algebra/simplifyCore.js +++ b/src/expression/embeddedDocs/function/algebra/simplifyCore.js @@ -10,6 +10,6 @@ export const simplifyCoreDocs = { 'simplifyCore(parse("(x+0)*2"))' ], seealso: [ - 'simplify', 'evaluate' + 'simplify', 'simplifyConstant', 'evaluate' ] } diff --git a/src/expression/node/AccessorNode.js b/src/expression/node/AccessorNode.js index 6adce6c0b4..b3c289986c 100644 --- a/src/expression/node/AccessorNode.js +++ b/src/expression/node/AccessorNode.js @@ -23,193 +23,190 @@ export const createAccessorNode = /* #__PURE__ */ factory(name, dependencies, ({ const access = accessFactory({ subset }) /** - * @constructor AccessorNode - * @extends {Node} - * Access an object property or get a matrix subset - * - * @param {Node} object The object from which to retrieve - * a property or subset. - * @param {IndexNode} index IndexNode containing ranges + * Are parenthesis needed? + * @private */ - function AccessorNode (object, index) { - if (!(this instanceof AccessorNode)) { - throw new SyntaxError('Constructor must be called with the new operator') - } + function needParenthesis (node) { + // TODO: maybe make a method on the nodes which tells whether they need parenthesis? + return !( + isAccessorNode(node) || + isArrayNode(node) || + isConstantNode(node) || + isFunctionNode(node) || + isObjectNode(node) || + isParenthesisNode(node) || + isSymbolNode(node)) + } - if (!isNode(object)) { - throw new TypeError('Node expected for parameter "object"') - } - if (!isIndexNode(index)) { - throw new TypeError('IndexNode expected for parameter "index"') - } + class AccessorNode extends Node { + /** + * @constructor AccessorNode + * @extends {Node} + * Access an object property or get a matrix subset + * + * @param {Node} object The object from which to retrieve + * a property or subset. + * @param {IndexNode} index IndexNode containing ranges + */ + constructor (object, index) { + super() + if (!isNode(object)) { + throw new TypeError('Node expected for parameter "object"') + } + if (!isIndexNode(index)) { + throw new TypeError('IndexNode expected for parameter "index"') + } - this.object = object || null - this.index = index + this.object = object + this.index = index + } // readonly property name - Object.defineProperty(this, 'name', { - get: function () { - if (this.index) { - return (this.index.isObjectProperty()) - ? this.index.getObjectProperty() - : '' - } else { - return this.object.name || '' - } - }.bind(this), - set: function () { - throw new Error('Cannot assign a new name, name is read-only') + get name () { + if (this.index) { + return (this.index.isObjectProperty()) + ? this.index.getObjectProperty() + : '' + } else { + return this.object.name || '' } - }) - } - - AccessorNode.prototype = new Node() - - AccessorNode.prototype.type = 'AccessorNode' - - AccessorNode.prototype.isAccessorNode = true + } - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - AccessorNode.prototype._compile = function (math, argNames) { - const evalObject = this.object._compile(math, argNames) - const evalIndex = this.index._compile(math, argNames) - - if (this.index.isObjectProperty()) { - const prop = this.index.getObjectProperty() - return function evalAccessorNode (scope, args, context) { - // get a property from an object evaluated using the scope. - return getSafeProperty(evalObject(scope, args, context), prop) - } - } else { - return function evalAccessorNode (scope, args, context) { - const object = evalObject(scope, args, context) - const index = evalIndex(scope, args, object) // we pass object here instead of context - return access(object, index) + static name = name + get type () { return name } + get isAccessorNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + const evalObject = this.object._compile(math, argNames) + const evalIndex = this.index._compile(math, argNames) + + if (this.index.isObjectProperty()) { + const prop = this.index.getObjectProperty() + return function evalAccessorNode (scope, args, context) { + // get a property from an object evaluated using the scope. + return getSafeProperty(evalObject(scope, args, context), prop) + } + } else { + return function evalAccessorNode (scope, args, context) { + const object = evalObject(scope, args, context) + // we pass just object here instead of context: + const index = evalIndex(scope, args, object) + return access(object, index) + } } } - } - - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - AccessorNode.prototype.forEach = function (callback) { - callback(this.object, 'object', this) - callback(this.index, 'index', this) - } - /** - * Create a new AccessorNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {AccessorNode} Returns a transformed copy of the node - */ - AccessorNode.prototype.map = function (callback) { - return new AccessorNode( - this._ifNode(callback(this.object, 'object', this)), - this._ifNode(callback(this.index, 'index', this)) - ) - } + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + callback(this.object, 'object', this) + callback(this.index, 'index', this) + } - /** - * Create a clone of this node, a shallow copy - * @return {AccessorNode} - */ - AccessorNode.prototype.clone = function () { - return new AccessorNode(this.object, this.index) - } + /** + * Create a new AccessorNode whose children are the results of calling + * the provided callback function for each child of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {AccessorNode} Returns a transformed copy of the node + */ + map (callback) { + return new AccessorNode( + this._ifNode(callback(this.object, 'object', this)), + this._ifNode(callback(this.index, 'index', this)) + ) + } - /** - * Get string representation - * @param {Object} options - * @return {string} - */ - AccessorNode.prototype._toString = function (options) { - let object = this.object.toString(options) - if (needParenthesis(this.object)) { - object = '(' + object + ')' + /** + * Create a clone of this node, a shallow copy + * @return {AccessorNode} + */ + clone () { + return new AccessorNode(this.object, this.index) } - return object + this.index.toString(options) - } + /** + * Get string representation + * @param {Object} options + * @return {string} + */ + _toString (options) { + let object = this.object.toString(options) + if (needParenthesis(this.object)) { + object = '(' + object + ')' + } - /** - * Get HTML representation - * @param {Object} options - * @return {string} - */ - AccessorNode.prototype.toHTML = function (options) { - let object = this.object.toHTML(options) - if (needParenthesis(this.object)) { - object = '(' + object + ')' + return object + this.index.toString(options) } - return object + this.index.toHTML(options) - } + /** + * Get HTML representation + * @param {Object} options + * @return {string} + */ + toHTML (options) { + let object = this.object.toHTML(options) + if (needParenthesis(this.object)) { + object = + '(' + + object + + ')' + } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} - */ - AccessorNode.prototype._toTex = function (options) { - let object = this.object.toTex(options) - if (needParenthesis(this.object)) { - object = '\\left(\' + object + \'\\right)' + return object + this.index.toHTML(options) } - return object + this.index.toTex(options) - } + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} + */ + _toTex (options) { + let object = this.object.toTex(options) + if (needParenthesis(this.object)) { + object = '\\left(\' + object + \'\\right)' + } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - AccessorNode.prototype.toJSON = function () { - return { - mathjs: 'AccessorNode', - object: this.object, - index: this.index + return object + this.index.toTex(options) } - } - /** - * Instantiate an AccessorNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "AccessorNode", object: ..., index: ...}`, - * where mathjs is optional - * @returns {AccessorNode} - */ - AccessorNode.fromJSON = function (json) { - return new AccessorNode(json.object, json.index) - } + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { + mathjs: name, + object: this.object, + index: this.index + } + } - /** - * Are parenthesis needed? - * @private - */ - function needParenthesis (node) { - // TODO: maybe make a method on the nodes which tells whether they need parenthesis? - return !( - isAccessorNode(node) || - isArrayNode(node) || - isConstantNode(node) || - isFunctionNode(node) || - isObjectNode(node) || - isParenthesisNode(node) || - isSymbolNode(node)) + /** + * Instantiate an AccessorNode from its JSON representation + * @param {Object} json + * An object structured like + * `{"mathjs": "AccessorNode", object: ..., index: ...}`, + * where mathjs is optional + * @returns {AccessorNode} + */ + static fromJSON (json) { + return new AccessorNode(json.object, json.index) + } } return AccessorNode diff --git a/src/expression/node/ArrayNode.js b/src/expression/node/ArrayNode.js index 25879c1530..800e536029 100644 --- a/src/expression/node/ArrayNode.js +++ b/src/expression/node/ArrayNode.js @@ -8,171 +8,170 @@ const dependencies = [ ] export const createArrayNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { - /** - * @constructor ArrayNode - * @extends {Node} - * Holds an 1-dimensional array with items - * @param {Node[]} [items] 1 dimensional array with items - */ - function ArrayNode (items) { - if (!(this instanceof ArrayNode)) { - throw new SyntaxError('Constructor must be called with the new operator') + class ArrayNode extends Node { + /** + * @constructor ArrayNode + * @extends {Node} + * Holds an 1-dimensional array with items + * @param {Node[]} [items] 1 dimensional array with items + */ + constructor (items) { + super() + this.items = items || [] + + // validate input + if (!Array.isArray(this.items) || !this.items.every(isNode)) { + throw new TypeError('Array containing Nodes expected') + } } - this.items = items || [] - - // validate input - if (!Array.isArray(this.items) || !this.items.every(isNode)) { - throw new TypeError('Array containing Nodes expected') + static name = name + get type () { return name } + get isArrayNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + const evalItems = map(this.items, function (item) { + return item._compile(math, argNames) + }) + + const asMatrix = (math.config.matrix !== 'Array') + if (asMatrix) { + const matrix = math.matrix + return function evalArrayNode (scope, args, context) { + return matrix(map(evalItems, function (evalItem) { + return evalItem(scope, args, context) + })) + } + } else { + return function evalArrayNode (scope, args, context) { + return map(evalItems, function (evalItem) { + return evalItem(scope, args, context) + }) + } + } } - } - - ArrayNode.prototype = new Node() - - ArrayNode.prototype.type = 'ArrayNode' - ArrayNode.prototype.isArrayNode = true - - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - ArrayNode.prototype._compile = function (math, argNames) { - const evalItems = map(this.items, function (item) { - return item._compile(math, argNames) - }) - - const asMatrix = (math.config.matrix !== 'Array') - if (asMatrix) { - const matrix = math.matrix - return function evalArrayNode (scope, args, context) { - return matrix(map(evalItems, function (evalItem) { - return evalItem(scope, args, context) - })) - } - } else { - return function evalArrayNode (scope, args, context) { - return map(evalItems, function (evalItem) { - return evalItem(scope, args, context) - }) + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + for (let i = 0; i < this.items.length; i++) { + const node = this.items[i] + callback(node, 'items[' + i + ']', this) } } - } - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - ArrayNode.prototype.forEach = function (callback) { - for (let i = 0; i < this.items.length; i++) { - const node = this.items[i] - callback(node, 'items[' + i + ']', this) + /** + * Create a new ArrayNode whose children are the results of calling + * the provided callback function for each child of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {ArrayNode} Returns a transformed copy of the node + */ + map (callback) { + const items = [] + for (let i = 0; i < this.items.length; i++) { + items[i] = this._ifNode(callback(this.items[i], 'items[' + i + ']', this)) + } + return new ArrayNode(items) } - } - /** - * Create a new ArrayNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {ArrayNode} Returns a transformed copy of the node - */ - ArrayNode.prototype.map = function (callback) { - const items = [] - for (let i = 0; i < this.items.length; i++) { - items[i] = this._ifNode(callback(this.items[i], 'items[' + i + ']', this)) + /** + * Create a clone of this node, a shallow copy + * @return {ArrayNode} + */ + clone () { + return new ArrayNode(this.items.slice(0)) } - return new ArrayNode(items) - } - - /** - * Create a clone of this node, a shallow copy - * @return {ArrayNode} - */ - ArrayNode.prototype.clone = function () { - return new ArrayNode(this.items.slice(0)) - } - /** - * Get string representation - * @param {Object} options - * @return {string} str - * @override - */ - ArrayNode.prototype._toString = function (options) { - const items = this.items.map(function (node) { - return node.toString(options) - }) - return '[' + items.join(', ') + ']' - } + /** + * Get string representation + * @param {Object} options + * @return {string} str + * @override + */ + _toString (options) { + const items = this.items.map(function (node) { + return node.toString(options) + }) + return '[' + items.join(', ') + ']' + } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - ArrayNode.prototype.toJSON = function () { - return { - mathjs: 'ArrayNode', - items: this.items + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { + mathjs: name, + items: this.items + } } - } - /** - * Instantiate an ArrayNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "ArrayNode", items: [...]}`, - * where mathjs is optional - * @returns {ArrayNode} - */ - ArrayNode.fromJSON = function (json) { - return new ArrayNode(json.items) - } + /** + * Instantiate an ArrayNode from its JSON representation + * @param {Object} json An object structured like + * `{"mathjs": "ArrayNode", items: [...]}`, + * where mathjs is optional + * @returns {ArrayNode} + */ + static fromJSON (json) { + return new ArrayNode(json.items) + } - /** - * Get HTML representation - * @param {Object} options - * @return {string} str - * @override - */ - ArrayNode.prototype.toHTML = function (options) { - const items = this.items.map(function (node) { - return node.toHTML(options) - }) - return '[' + items.join(',') + ']' - } + /** + * Get HTML representation + * @param {Object} options + * @return {string} str + * @override + */ + toHTML (options) { + const items = this.items.map(function (node) { + return node.toHTML(options) + }) + return '[' + + items.join(',') + + ']' + } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} str - */ - ArrayNode.prototype._toTex = function (options) { - function itemsToTex (items, nested) { - const mixedItems = items.some(isArrayNode) && !items.every(isArrayNode) - const itemsFormRow = nested || mixedItems - const itemSep = itemsFormRow ? '&' : '\\\\' - const itemsTex = items - .map(function (node) { - if (node.items) { - return itemsToTex(node.items, !nested) - } else { - return node.toTex(options) - } - }) - .join(itemSep) - return mixedItems || !itemsFormRow || (itemsFormRow && !nested) - ? '\\begin{bmatrix}' + itemsTex + '\\end{bmatrix}' - : itemsTex + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} str + */ + _toTex (options) { + function itemsToTex (items, nested) { + const mixedItems = items.some(isArrayNode) && !items.every(isArrayNode) + const itemsFormRow = nested || mixedItems + const itemSep = itemsFormRow ? '&' : '\\\\' + const itemsTex = items + .map(function (node) { + if (node.items) { + return itemsToTex(node.items, !nested) + } else { + return node.toTex(options) + } + }) + .join(itemSep) + return mixedItems || !itemsFormRow || (itemsFormRow && !nested) + ? '\\begin{bmatrix}' + itemsTex + '\\end{bmatrix}' + : itemsTex + } + return itemsToTex(this.items, false) } - return itemsToTex(this.items, false) } return ArrayNode diff --git a/src/expression/node/AssignmentNode.js b/src/expression/node/AssignmentNode.js index 8572be6b4c..87805f110f 100644 --- a/src/expression/node/AssignmentNode.js +++ b/src/expression/node/AssignmentNode.js @@ -16,288 +16,304 @@ export const createAssignmentNode = /* #__PURE__ */ factory(name, dependencies, const access = accessFactory({ subset }) const assign = assignFactory({ subset, matrix }) - /** - * @constructor AssignmentNode - * @extends {Node} - * - * Define a symbol, like `a=3.2`, update a property like `a.b=3.2`, or - * replace a subset of a matrix like `A[2,2]=42`. - * - * Syntax: - * - * new AssignmentNode(symbol, value) - * new AssignmentNode(object, index, value) - * - * Usage: - * - * new AssignmentNode(new SymbolNode('a'), new ConstantNode(2)) // a=2 - * new AssignmentNode(new SymbolNode('a'), new IndexNode('b'), new ConstantNode(2)) // a.b=2 - * new AssignmentNode(new SymbolNode('a'), new IndexNode(1, 2), new ConstantNode(3)) // a[1,2]=3 - * - * @param {SymbolNode | AccessorNode} object Object on which to assign a value - * @param {IndexNode} [index=null] Index, property name or matrix - * index. Optional. If not provided - * and `object` is a SymbolNode, - * the property is assigned to the - * global scope. - * @param {Node} value The value to be assigned + /* + * Is parenthesis needed? + * @param {node} node + * @param {string} [parenthesis='keep'] + * @param {string} implicit + * @private */ - function AssignmentNode (object, index, value) { - if (!(this instanceof AssignmentNode)) { - throw new SyntaxError('Constructor must be called with the new operator') - } - - this.object = object - this.index = value ? index : null - this.value = value || index - - // validate input - if (!isSymbolNode(object) && !isAccessorNode(object)) { - throw new TypeError('SymbolNode or AccessorNode expected as "object"') - } - if (isSymbolNode(object) && object.name === 'end') { - throw new Error('Cannot assign to symbol "end"') - } - if (this.index && !isIndexNode(this.index)) { // index is optional - throw new TypeError('IndexNode expected as "index"') - } - if (!isNode(this.value)) { - throw new TypeError('Node expected as "value"') + function needParenthesis (node, parenthesis, implicit) { + if (!parenthesis) { + parenthesis = 'keep' } - // readonly property name - Object.defineProperty(this, 'name', { - get: function () { - if (this.index) { - return (this.index.isObjectProperty()) - ? this.index.getObjectProperty() - : '' - } else { - return this.object.name || '' - } - }.bind(this), - set: function () { - throw new Error('Cannot assign a new name, name is read-only') - } - }) + const precedence = getPrecedence(node, parenthesis, implicit) + const exprPrecedence = getPrecedence(node.value, parenthesis, implicit) + return (parenthesis === 'all') || + ((exprPrecedence !== null) && (exprPrecedence <= precedence)) } - AssignmentNode.prototype = new Node() - - AssignmentNode.prototype.type = 'AssignmentNode' - - AssignmentNode.prototype.isAssignmentNode = true - - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - AssignmentNode.prototype._compile = function (math, argNames) { - const evalObject = this.object._compile(math, argNames) - const evalIndex = this.index ? this.index._compile(math, argNames) : null - const evalValue = this.value._compile(math, argNames) - const name = this.object.name - - if (!this.index) { - // apply a variable to the scope, for example `a=2` - if (!isSymbolNode(this.object)) { - throw new TypeError('SymbolNode expected as object') + class AssignmentNode extends Node { + /** + * @constructor AssignmentNode + * @extends {Node} + * + * Define a symbol, like `a=3.2`, update a property like `a.b=3.2`, or + * replace a subset of a matrix like `A[2,2]=42`. + * + * Syntax: + * + * new AssignmentNode(symbol, value) + * new AssignmentNode(object, index, value) + * + * Usage: + * + * new AssignmentNode(new SymbolNode('a'), new ConstantNode(2)) // a=2 + * new AssignmentNode(new SymbolNode('a'), + * new IndexNode('b'), + * new ConstantNode(2)) // a.b=2 + * new AssignmentNode(new SymbolNode('a'), + * new IndexNode(1, 2), + * new ConstantNode(3)) // a[1,2]=3 + * + * @param {SymbolNode | AccessorNode} object + * Object on which to assign a value + * @param {IndexNode} [index=null] + * Index, property name or matrix index. Optional. If not provided + * and `object` is a SymbolNode, the property is assigned to the + * global scope. + * @param {Node} value + * The value to be assigned + */ + constructor (object, index, value) { + super() + this.object = object + this.index = value ? index : null + this.value = value || index + + // validate input + if (!isSymbolNode(object) && !isAccessorNode(object)) { + throw new TypeError('SymbolNode or AccessorNode expected as "object"') } - - return function evalAssignmentNode (scope, args, context) { - const value = evalValue(scope, args, context) - scope.set(name, value) - return value + if (isSymbolNode(object) && object.name === 'end') { + throw new Error('Cannot assign to symbol "end"') } - } else if (this.index.isObjectProperty()) { - // apply an object property for example `a.b=2` - const prop = this.index.getObjectProperty() - - return function evalAssignmentNode (scope, args, context) { - const object = evalObject(scope, args, context) - const value = evalValue(scope, args, context) - setSafeProperty(object, prop, value) - return value + if (this.index && !isIndexNode(this.index)) { // index is optional + throw new TypeError('IndexNode expected as "index"') } - } else if (isSymbolNode(this.object)) { - // update a matrix subset, for example `a[2]=3` - return function evalAssignmentNode (scope, args, context) { - const childObject = evalObject(scope, args, context) - const value = evalValue(scope, args, context) - const index = evalIndex(scope, args, childObject) // Important: we pass childObject instead of context - scope.set(name, assign(childObject, index, value)) - return value + if (!isNode(this.value)) { + throw new TypeError('Node expected as "value"') } - } else { // isAccessorNode(node.object) === true - // update a matrix subset, for example `a.b[2]=3` + } - // we will not use the compile function of the AccessorNode, but compile it - // ourselves here as we need the parent object of the AccessorNode: - // wee need to apply the updated object to parent object - const evalParentObject = this.object.object._compile(math, argNames) + // class name for typing purposes: + static name = name - if (this.object.index.isObjectProperty()) { - const parentProp = this.object.index.getObjectProperty() + // readonly property name + get name () { + if (this.index) { + return (this.index.isObjectProperty()) + ? this.index.getObjectProperty() + : '' + } else { + return this.object.name || '' + } + } + + get type () { return name } + get isAssignmentNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + const evalObject = this.object._compile(math, argNames) + const evalIndex = this.index ? this.index._compile(math, argNames) : null + const evalValue = this.value._compile(math, argNames) + const name = this.object.name + + if (!this.index) { + // apply a variable to the scope, for example `a=2` + if (!isSymbolNode(this.object)) { + throw new TypeError('SymbolNode expected as object') + } return function evalAssignmentNode (scope, args, context) { - const parent = evalParentObject(scope, args, context) - const childObject = getSafeProperty(parent, parentProp) - const index = evalIndex(scope, args, childObject) // Important: we pass childObject instead of context const value = evalValue(scope, args, context) - setSafeProperty(parent, parentProp, assign(childObject, index, value)) + scope.set(name, value) return value } - } else { - // if some parameters use the 'end' parameter, we need to calculate the size - const evalParentIndex = this.object.index._compile(math, argNames) + } else if (this.index.isObjectProperty()) { + // apply an object property for example `a.b=2` + const prop = this.index.getObjectProperty() return function evalAssignmentNode (scope, args, context) { - const parent = evalParentObject(scope, args, context) - const parentIndex = evalParentIndex(scope, args, parent) // Important: we pass parent instead of context - const childObject = access(parent, parentIndex) - const index = evalIndex(scope, args, childObject) // Important: we pass childObject instead of context + const object = evalObject(scope, args, context) const value = evalValue(scope, args, context) - - assign(parent, parentIndex, assign(childObject, index, value)) - + setSafeProperty(object, prop, value) + return value + } + } else if (isSymbolNode(this.object)) { + // update a matrix subset, for example `a[2]=3` + return function evalAssignmentNode (scope, args, context) { + const childObject = evalObject(scope, args, context) + const value = evalValue(scope, args, context) + // Important: we pass childObject instead of context: + const index = evalIndex(scope, args, childObject) + scope.set(name, assign(childObject, index, value)) return value } + } else { // isAccessorNode(node.object) === true + // update a matrix subset, for example `a.b[2]=3` + + // we will not use the compile function of the AccessorNode, but + // compile it ourselves here as we need the parent object of the + // AccessorNode: + // wee need to apply the updated object to parent object + const evalParentObject = this.object.object._compile(math, argNames) + + if (this.object.index.isObjectProperty()) { + const parentProp = this.object.index.getObjectProperty() + + return function evalAssignmentNode (scope, args, context) { + const parent = evalParentObject(scope, args, context) + const childObject = getSafeProperty(parent, parentProp) + // Important: we pass childObject instead of context: + const index = evalIndex(scope, args, childObject) + const value = evalValue(scope, args, context) + setSafeProperty( + parent, parentProp, assign(childObject, index, value)) + return value + } + } else { + // if some parameters use the 'end' parameter, we need to calculate + // the size + const evalParentIndex = this.object.index._compile(math, argNames) + + return function evalAssignmentNode (scope, args, context) { + const parent = evalParentObject(scope, args, context) + // Important: we pass parent instead of context: + const parentIndex = evalParentIndex(scope, args, parent) + const childObject = access(parent, parentIndex) + // Important: we pass childObject instead of context + const index = evalIndex(scope, args, childObject) + const value = evalValue(scope, args, context) + + assign(parent, parentIndex, assign(childObject, index, value)) + + return value + } + } } } - } - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - AssignmentNode.prototype.forEach = function (callback) { - callback(this.object, 'object', this) - if (this.index) { - callback(this.index, 'index', this) + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + callback(this.object, 'object', this) + if (this.index) { + callback(this.index, 'index', this) + } + callback(this.value, 'value', this) } - callback(this.value, 'value', this) - } - /** - * Create a new AssignmentNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {AssignmentNode} Returns a transformed copy of the node - */ - AssignmentNode.prototype.map = function (callback) { - const object = this._ifNode(callback(this.object, 'object', this)) - const index = this.index - ? this._ifNode(callback(this.index, 'index', this)) - : null - const value = this._ifNode(callback(this.value, 'value', this)) - - return new AssignmentNode(object, index, value) - } - - /** - * Create a clone of this node, a shallow copy - * @return {AssignmentNode} - */ - AssignmentNode.prototype.clone = function () { - return new AssignmentNode(this.object, this.index, this.value) - } + /** + * Create a new AssignmentNode whose children are the results of calling + * the provided callback function for each child of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {AssignmentNode} Returns a transformed copy of the node + */ + map (callback) { + const object = this._ifNode(callback(this.object, 'object', this)) + const index = this.index + ? this._ifNode(callback(this.index, 'index', this)) + : null + const value = this._ifNode(callback(this.value, 'value', this)) + + return new AssignmentNode(object, index, value) + } - /* - * Is parenthesis needed? - * @param {node} node - * @param {string} [parenthesis='keep'] - * @private - */ - function needParenthesis (node, parenthesis) { - if (!parenthesis) { - parenthesis = 'keep' + /** + * Create a clone of this node, a shallow copy + * @return {AssignmentNode} + */ + clone () { + return new AssignmentNode(this.object, this.index, this.value) } - const precedence = getPrecedence(node, parenthesis) - const exprPrecedence = getPrecedence(node.value, parenthesis) - return (parenthesis === 'all') || - ((exprPrecedence !== null) && (exprPrecedence <= precedence)) - } + /** + * Get string representation + * @param {Object} options + * @return {string} + */ + _toString (options) { + const object = this.object.toString(options) + const index = this.index ? this.index.toString(options) : '' + let value = this.value.toString(options) + if (needParenthesis( + this, options && options.parenthesis, options && options.implicit)) { + value = '(' + value + ')' + } - /** - * Get string representation - * @param {Object} options - * @return {string} - */ - AssignmentNode.prototype._toString = function (options) { - const object = this.object.toString(options) - const index = this.index ? this.index.toString(options) : '' - let value = this.value.toString(options) - if (needParenthesis(this, options && options.parenthesis)) { - value = '(' + value + ')' + return object + index + ' = ' + value } - return object + index + ' = ' + value - } + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { + mathjs: name, + object: this.object, + index: this.index, + value: this.value + } + } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - AssignmentNode.prototype.toJSON = function () { - return { - mathjs: 'AssignmentNode', - object: this.object, - index: this.index, - value: this.value + /** + * Instantiate an AssignmentNode from its JSON representation + * @param {Object} json + * An object structured like + * `{"mathjs": "AssignmentNode", object: ..., index: ..., value: ...}`, + * where mathjs is optional + * @returns {AssignmentNode} + */ + static fromJSON (json) { + return new AssignmentNode(json.object, json.index, json.value) } - } - /** - * Instantiate an AssignmentNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "AssignmentNode", object: ..., index: ..., value: ...}`, - * where mathjs is optional - * @returns {AssignmentNode} - */ - AssignmentNode.fromJSON = function (json) { - return new AssignmentNode(json.object, json.index, json.value) - } + /** + * Get HTML representation + * @param {Object} options + * @return {string} + */ + toHTML (options) { + const object = this.object.toHTML(options) + const index = this.index ? this.index.toHTML(options) : '' + let value = this.value.toHTML(options) + if (needParenthesis( + this, options && options.parenthesis, options && options.implicit)) { + value = '(' + + value + + ')' + } - /** - * Get HTML representation - * @param {Object} options - * @return {string} - */ - AssignmentNode.prototype.toHTML = function (options) { - const object = this.object.toHTML(options) - const index = this.index ? this.index.toHTML(options) : '' - let value = this.value.toHTML(options) - if (needParenthesis(this, options && options.parenthesis)) { - value = '(' + value + ')' + return object + index + + '=' + + value } - return object + index + '=' + value - } + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} + */ + _toTex (options) { + const object = this.object.toTex(options) + const index = this.index ? this.index.toTex(options) : '' + let value = this.value.toTex(options) + if (needParenthesis( + this, options && options.parenthesis, options && options.implicit)) { + value = `\\left(${value}\\right)` + } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} - */ - AssignmentNode.prototype._toTex = function (options) { - const object = this.object.toTex(options) - const index = this.index ? this.index.toTex(options) : '' - let value = this.value.toTex(options) - if (needParenthesis(this, options && options.parenthesis)) { - value = `\\left(${value}\\right)` + return object + index + ':=' + value } - - return object + index + ':=' + value } return AssignmentNode diff --git a/src/expression/node/BlockNode.js b/src/expression/node/BlockNode.js index a55691e185..75b041d916 100644 --- a/src/expression/node/BlockNode.js +++ b/src/expression/node/BlockNode.js @@ -9,173 +9,177 @@ const dependencies = [ ] export const createBlockNode = /* #__PURE__ */ factory(name, dependencies, ({ ResultSet, Node }) => { - /** - * @constructor BlockNode - * @extends {Node} - * Holds a set with blocks - * @param {Array.<{node: Node} | {node: Node, visible: boolean}>} blocks - * An array with blocks, where a block is constructed as an Object - * with properties block, which is a Node, and visible, which is - * a boolean. The property visible is optional and is true by default - */ - function BlockNode (blocks) { - if (!(this instanceof BlockNode)) { - throw new SyntaxError('Constructor must be called with the new operator') + class BlockNode extends Node { + /** + * @constructor BlockNode + * @extends {Node} + * Holds a set with blocks + * @param {Array.<{node: Node} | {node: Node, visible: boolean}>} blocks + * An array with blocks, where a block is constructed as an + * Object with properties block, which is a Node, and visible, + * which is a boolean. The property visible is optional and + * is true by default + */ + constructor (blocks) { + super() + // validate input, copy blocks + if (!Array.isArray(blocks)) throw new Error('Array expected') + this.blocks = blocks.map(function (block) { + const node = block && block.node + const visible = block && + block.visible !== undefined + ? block.visible + : true + + if (!isNode(node)) throw new TypeError('Property "node" must be a Node') + if (typeof visible !== 'boolean') { throw new TypeError('Property "visible" must be a boolean') } + + return { node, visible } + }) } - // validate input, copy blocks - if (!Array.isArray(blocks)) throw new Error('Array expected') - this.blocks = blocks.map(function (block) { - const node = block && block.node - const visible = block && block.visible !== undefined ? block.visible : true + static name = name + get type () { return name } + get isBlockNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + const evalBlocks = map(this.blocks, function (block) { + return { + evaluate: block.node._compile(math, argNames), + visible: block.visible + } + }) - if (!isNode(node)) throw new TypeError('Property "node" must be a Node') - if (typeof visible !== 'boolean') throw new TypeError('Property "visible" must be a boolean') + return function evalBlockNodes (scope, args, context) { + const results = [] - return { node, visible } - }) - } + forEach(evalBlocks, function evalBlockNode (block) { + const result = block.evaluate(scope, args, context) + if (block.visible) { + results.push(result) + } + }) - BlockNode.prototype = new Node() - - BlockNode.prototype.type = 'BlockNode' - - BlockNode.prototype.isBlockNode = true - - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - BlockNode.prototype._compile = function (math, argNames) { - const evalBlocks = map(this.blocks, function (block) { - return { - evaluate: block.node._compile(math, argNames), - visible: block.visible + return new ResultSet(results) } - }) + } - return function evalBlockNodes (scope, args, context) { - const results = [] + /** + * Execute a callback for each of the child blocks of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + for (let i = 0; i < this.blocks.length; i++) { + callback(this.blocks[i].node, 'blocks[' + i + '].node', this) + } + } - forEach(evalBlocks, function evalBlockNode (block) { - const result = block.evaluate(scope, args, context) - if (block.visible) { - results.push(result) + /** + * Create a new BlockNode whose children are the results of calling + * the provided callback function for each child of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {BlockNode} Returns a transformed copy of the node + */ + map (callback) { + const blocks = [] + for (let i = 0; i < this.blocks.length; i++) { + const block = this.blocks[i] + const node = this._ifNode( + callback(block.node, 'blocks[' + i + '].node', this)) + blocks[i] = { + node, + visible: block.visible + } + } + return new BlockNode(blocks) + } + + /** + * Create a clone of this node, a shallow copy + * @return {BlockNode} + */ + clone () { + const blocks = this.blocks.map(function (block) { + return { + node: block.node, + visible: block.visible } }) - return new ResultSet(results) + return new BlockNode(blocks) } - } - /** - * Execute a callback for each of the child blocks of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - BlockNode.prototype.forEach = function (callback) { - for (let i = 0; i < this.blocks.length; i++) { - callback(this.blocks[i].node, 'blocks[' + i + '].node', this) + /** + * Get string representation + * @param {Object} options + * @return {string} str + * @override + */ + _toString (options) { + return this.blocks.map(function (param) { + return param.node.toString(options) + (param.visible ? '' : ';') + }).join('\n') } - } - /** - * Create a new BlockNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {BlockNode} Returns a transformed copy of the node - */ - BlockNode.prototype.map = function (callback) { - const blocks = [] - for (let i = 0; i < this.blocks.length; i++) { - const block = this.blocks[i] - const node = this._ifNode(callback(block.node, 'blocks[' + i + '].node', this)) - blocks[i] = { - node, - visible: block.visible - } - } - return new BlockNode(blocks) - } - - /** - * Create a clone of this node, a shallow copy - * @return {BlockNode} - */ - BlockNode.prototype.clone = function () { - const blocks = this.blocks.map(function (block) { + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { return { - node: block.node, - visible: block.visible + mathjs: name, + blocks: this.blocks } - }) - - return new BlockNode(blocks) - } - - /** - * Get string representation - * @param {Object} options - * @return {string} str - * @override - */ - BlockNode.prototype._toString = function (options) { - return this.blocks.map(function (param) { - return param.node.toString(options) + (param.visible ? '' : ';') - }).join('\n') - } - - /** - * Get a JSON representation of the node - * @returns {Object} - */ - BlockNode.prototype.toJSON = function () { - return { - mathjs: 'BlockNode', - blocks: this.blocks } - } - /** - * Instantiate an BlockNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "BlockNode", blocks: [{node: ..., visible: false}, ...]}`, - * where mathjs is optional - * @returns {BlockNode} - */ - BlockNode.fromJSON = function (json) { - return new BlockNode(json.blocks) - } + /** + * Instantiate an BlockNode from its JSON representation + * @param {Object} json + * An object structured like + * `{"mathjs": "BlockNode", blocks: [{node: ..., visible: false}, ...]}`, + * where mathjs is optional + * @returns {BlockNode} + */ + static fromJSON (json) { + return new BlockNode(json.blocks) + } - /** - * Get HTML representation - * @param {Object} options - * @return {string} str - * @override - */ - BlockNode.prototype.toHTML = function (options) { - return this.blocks.map(function (param) { - return param.node.toHTML(options) + (param.visible ? '' : ';') - }).join('
') - } + /** + * Get HTML representation + * @param {Object} options + * @return {string} str + * @override + */ + toHTML (options) { + return this.blocks.map(function (param) { + return param.node.toHTML(options) + + (param.visible ? '' : ';') + }).join('
') + } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} str - */ - BlockNode.prototype._toTex = function (options) { - return this.blocks.map(function (param) { - return param.node.toTex(options) + (param.visible ? '' : ';') - }).join('\\;\\;\n') + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} str + */ + _toTex (options) { + return this.blocks.map(function (param) { + return param.node.toTex(options) + (param.visible ? '' : ';') + }).join('\\;\\;\n') + } } return BlockNode diff --git a/src/expression/node/ConditionalNode.js b/src/expression/node/ConditionalNode.js index 8f0ead9221..121f4df6b4 100644 --- a/src/expression/node/ConditionalNode.js +++ b/src/expression/node/ConditionalNode.js @@ -9,237 +9,266 @@ const dependencies = [ export const createConditionalNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { /** - * A lazy evaluating conditional operator: 'condition ? trueExpr : falseExpr' - * - * @param {Node} condition Condition, must result in a boolean - * @param {Node} trueExpr Expression evaluated when condition is true - * @param {Node} falseExpr Expression evaluated when condition is true - * - * @constructor ConditionalNode - * @extends {Node} + * Test whether a condition is met + * @param {*} condition + * @returns {boolean} true if condition is true or non-zero, else false */ - function ConditionalNode (condition, trueExpr, falseExpr) { - if (!(this instanceof ConditionalNode)) { - throw new SyntaxError('Constructor must be called with the new operator') + function testCondition (condition) { + if (typeof condition === 'number' || + typeof condition === 'boolean' || + typeof condition === 'string') { + return !!condition } - if (!isNode(condition)) throw new TypeError('Parameter condition must be a Node') - if (!isNode(trueExpr)) throw new TypeError('Parameter trueExpr must be a Node') - if (!isNode(falseExpr)) throw new TypeError('Parameter falseExpr must be a Node') - - this.condition = condition - this.trueExpr = trueExpr - this.falseExpr = falseExpr - } - ConditionalNode.prototype = new Node() + if (condition) { + if (isBigNumber(condition)) { + return !condition.isZero() + } - ConditionalNode.prototype.type = 'ConditionalNode' + if (isComplex(condition)) { + return !!((condition.re || condition.im)) + } - ConditionalNode.prototype.isConditionalNode = true + if (isUnit(condition)) { + return !!condition.value + } + } - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - ConditionalNode.prototype._compile = function (math, argNames) { - const evalCondition = this.condition._compile(math, argNames) - const evalTrueExpr = this.trueExpr._compile(math, argNames) - const evalFalseExpr = this.falseExpr._compile(math, argNames) - - return function evalConditionalNode (scope, args, context) { - return testCondition(evalCondition(scope, args, context)) - ? evalTrueExpr(scope, args, context) - : evalFalseExpr(scope, args, context) + if (condition === null || condition === undefined) { + return false } - } - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - ConditionalNode.prototype.forEach = function (callback) { - callback(this.condition, 'condition', this) - callback(this.trueExpr, 'trueExpr', this) - callback(this.falseExpr, 'falseExpr', this) + throw new TypeError('Unsupported type of condition "' + typeOf(condition) + '"') } - /** - * Create a new ConditionalNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {ConditionalNode} Returns a transformed copy of the node - */ - ConditionalNode.prototype.map = function (callback) { - return new ConditionalNode( - this._ifNode(callback(this.condition, 'condition', this)), - this._ifNode(callback(this.trueExpr, 'trueExpr', this)), - this._ifNode(callback(this.falseExpr, 'falseExpr', this)) - ) - } + class ConditionalNode extends Node { + /** + * A lazy evaluating conditional operator: 'condition ? trueExpr : falseExpr' + * + * @param {Node} condition Condition, must result in a boolean + * @param {Node} trueExpr Expression evaluated when condition is true + * @param {Node} falseExpr Expression evaluated when condition is true + * + * @constructor ConditionalNode + * @extends {Node} + */ + constructor (condition, trueExpr, falseExpr) { + super() + if (!isNode(condition)) { throw new TypeError('Parameter condition must be a Node') } + if (!isNode(trueExpr)) { throw new TypeError('Parameter trueExpr must be a Node') } + if (!isNode(falseExpr)) { throw new TypeError('Parameter falseExpr must be a Node') } - /** - * Create a clone of this node, a shallow copy - * @return {ConditionalNode} - */ - ConditionalNode.prototype.clone = function () { - return new ConditionalNode(this.condition, this.trueExpr, this.falseExpr) - } + this.condition = condition + this.trueExpr = trueExpr + this.falseExpr = falseExpr + } - /** - * Get string representation - * @param {Object} options - * @return {string} str - */ - ConditionalNode.prototype._toString = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - const precedence = getPrecedence(this, parenthesis) - - // Enclose Arguments in parentheses if they are an OperatorNode - // or have lower or equal precedence - // NOTE: enclosing all OperatorNodes in parentheses is a decision - // purely based on aesthetics and readability - let condition = this.condition.toString(options) - const conditionPrecedence = getPrecedence(this.condition, parenthesis) - if ((parenthesis === 'all') || - (this.condition.type === 'OperatorNode') || - ((conditionPrecedence !== null) && (conditionPrecedence <= precedence))) { - condition = '(' + condition + ')' + static name = name + get type () { return name } + get isConditionalNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + const evalCondition = this.condition._compile(math, argNames) + const evalTrueExpr = this.trueExpr._compile(math, argNames) + const evalFalseExpr = this.falseExpr._compile(math, argNames) + + return function evalConditionalNode (scope, args, context) { + return testCondition(evalCondition(scope, args, context)) + ? evalTrueExpr(scope, args, context) + : evalFalseExpr(scope, args, context) + } } - let trueExpr = this.trueExpr.toString(options) - const truePrecedence = getPrecedence(this.trueExpr, parenthesis) - if ((parenthesis === 'all') || - (this.trueExpr.type === 'OperatorNode') || - ((truePrecedence !== null) && (truePrecedence <= precedence))) { - trueExpr = '(' + trueExpr + ')' + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + callback(this.condition, 'condition', this) + callback(this.trueExpr, 'trueExpr', this) + callback(this.falseExpr, 'falseExpr', this) } - let falseExpr = this.falseExpr.toString(options) - const falsePrecedence = getPrecedence(this.falseExpr, parenthesis) - if ((parenthesis === 'all') || - (this.falseExpr.type === 'OperatorNode') || - ((falsePrecedence !== null) && (falsePrecedence <= precedence))) { - falseExpr = '(' + falseExpr + ')' + /** + * Create a new ConditionalNode whose children are the results of calling + * the provided callback function for each child of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {ConditionalNode} Returns a transformed copy of the node + */ + map (callback) { + return new ConditionalNode( + this._ifNode(callback(this.condition, 'condition', this)), + this._ifNode(callback(this.trueExpr, 'trueExpr', this)), + this._ifNode(callback(this.falseExpr, 'falseExpr', this)) + ) } - return condition + ' ? ' + trueExpr + ' : ' + falseExpr - } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - ConditionalNode.prototype.toJSON = function () { - return { - mathjs: 'ConditionalNode', - condition: this.condition, - trueExpr: this.trueExpr, - falseExpr: this.falseExpr + /** + * Create a clone of this node, a shallow copy + * @return {ConditionalNode} + */ + clone () { + return new ConditionalNode(this.condition, this.trueExpr, this.falseExpr) } - } - /** - * Instantiate an ConditionalNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "ConditionalNode", "condition": ..., "trueExpr": ..., "falseExpr": ...}`, - * where mathjs is optional - * @returns {ConditionalNode} - */ - ConditionalNode.fromJSON = function (json) { - return new ConditionalNode(json.condition, json.trueExpr, json.falseExpr) - } + /** + * Get string representation + * @param {Object} options + * @return {string} str + */ + _toString (options) { + const parenthesis = + (options && options.parenthesis) ? options.parenthesis : 'keep' + const precedence = + getPrecedence(this, parenthesis, options && options.implicit) - /** - * Get HTML representation - * @param {Object} options - * @return {string} str - */ - ConditionalNode.prototype.toHTML = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - const precedence = getPrecedence(this, parenthesis) - - // Enclose Arguments in parentheses if they are an OperatorNode - // or have lower or equal precedence - // NOTE: enclosing all OperatorNodes in parentheses is a decision - // purely based on aesthetics and readability - let condition = this.condition.toHTML(options) - const conditionPrecedence = getPrecedence(this.condition, parenthesis) - if ((parenthesis === 'all') || - (this.condition.type === 'OperatorNode') || - ((conditionPrecedence !== null) && (conditionPrecedence <= precedence))) { - condition = '(' + condition + ')' - } + // Enclose Arguments in parentheses if they are an OperatorNode + // or have lower or equal precedence + // NOTE: enclosing all OperatorNodes in parentheses is a decision + // purely based on aesthetics and readability + let condition = this.condition.toString(options) + const conditionPrecedence = + getPrecedence(this.condition, parenthesis, options && options.implicit) + if ((parenthesis === 'all') || + (this.condition.type === 'OperatorNode') || + ((conditionPrecedence !== null) && + (conditionPrecedence <= precedence))) { + condition = '(' + condition + ')' + } - let trueExpr = this.trueExpr.toHTML(options) - const truePrecedence = getPrecedence(this.trueExpr, parenthesis) - if ((parenthesis === 'all') || - (this.trueExpr.type === 'OperatorNode') || - ((truePrecedence !== null) && (truePrecedence <= precedence))) { - trueExpr = '(' + trueExpr + ')' - } + let trueExpr = this.trueExpr.toString(options) + const truePrecedence = + getPrecedence(this.trueExpr, parenthesis, options && options.implicit) + if ((parenthesis === 'all') || + (this.trueExpr.type === 'OperatorNode') || + ((truePrecedence !== null) && (truePrecedence <= precedence))) { + trueExpr = '(' + trueExpr + ')' + } - let falseExpr = this.falseExpr.toHTML(options) - const falsePrecedence = getPrecedence(this.falseExpr, parenthesis) - if ((parenthesis === 'all') || - (this.falseExpr.type === 'OperatorNode') || - ((falsePrecedence !== null) && (falsePrecedence <= precedence))) { - falseExpr = '(' + falseExpr + ')' + let falseExpr = this.falseExpr.toString(options) + const falsePrecedence = + getPrecedence(this.falseExpr, parenthesis, options && options.implicit) + if ((parenthesis === 'all') || + (this.falseExpr.type === 'OperatorNode') || + ((falsePrecedence !== null) && (falsePrecedence <= precedence))) { + falseExpr = '(' + falseExpr + ')' + } + return condition + ' ? ' + trueExpr + ' : ' + falseExpr } - return condition + '?' + trueExpr + ':' + falseExpr - } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} str - */ - ConditionalNode.prototype._toTex = function (options) { - return '\\begin{cases} {' + - this.trueExpr.toTex(options) + '}, &\\quad{\\text{if }\\;' + - this.condition.toTex(options) + - '}\\\\{' + this.falseExpr.toTex(options) + - '}, &\\quad{\\text{otherwise}}\\end{cases}' - } + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { + mathjs: name, + condition: this.condition, + trueExpr: this.trueExpr, + falseExpr: this.falseExpr + } + } - /** - * Test whether a condition is met - * @param {*} condition - * @returns {boolean} true if condition is true or non-zero, else false - */ - function testCondition (condition) { - if (typeof condition === 'number' || - typeof condition === 'boolean' || - typeof condition === 'string') { - return !!condition + /** + * Instantiate an ConditionalNode from its JSON representation + * @param {Object} json + * An object structured like + * ``` + * {"mathjs": "ConditionalNode", + * "condition": ..., + * "trueExpr": ..., + * "falseExpr": ...} + * ``` + * where mathjs is optional + * @returns {ConditionalNode} + */ + static fromJSON (json) { + return new ConditionalNode(json.condition, json.trueExpr, json.falseExpr) } - if (condition) { - if (isBigNumber(condition)) { - return !condition.isZero() + /** + * Get HTML representation + * @param {Object} options + * @return {string} str + */ + toHTML (options) { + const parenthesis = + (options && options.parenthesis) ? options.parenthesis : 'keep' + const precedence = + getPrecedence(this, parenthesis, options && options.implicit) + + // Enclose Arguments in parentheses if they are an OperatorNode + // or have lower or equal precedence + // NOTE: enclosing all OperatorNodes in parentheses is a decision + // purely based on aesthetics and readability + let condition = this.condition.toHTML(options) + const conditionPrecedence = + getPrecedence(this.condition, parenthesis, options && options.implicit) + if ((parenthesis === 'all') || + (this.condition.type === 'OperatorNode') || + ((conditionPrecedence !== null) && + (conditionPrecedence <= precedence))) { + condition = + '(' + + condition + + ')' } - if (isComplex(condition)) { - return !!((condition.re || condition.im)) + let trueExpr = this.trueExpr.toHTML(options) + const truePrecedence = + getPrecedence(this.trueExpr, parenthesis, options && options.implicit) + if ((parenthesis === 'all') || + (this.trueExpr.type === 'OperatorNode') || + ((truePrecedence !== null) && (truePrecedence <= precedence))) { + trueExpr = + '(' + + trueExpr + + ')' } - if (isUnit(condition)) { - return !!condition.value + let falseExpr = this.falseExpr.toHTML(options) + const falsePrecedence = + getPrecedence(this.falseExpr, parenthesis, options && options.implicit) + if ((parenthesis === 'all') || + (this.falseExpr.type === 'OperatorNode') || + ((falsePrecedence !== null) && (falsePrecedence <= precedence))) { + falseExpr = + '(' + + falseExpr + + ')' } + return condition + + '?' + + trueExpr + + ':' + + falseExpr } - if (condition === null || condition === undefined) { - return false + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} str + */ + _toTex (options) { + return '\\begin{cases} {' + + this.trueExpr.toTex(options) + '}, &\\quad{\\text{if }\\;' + + this.condition.toTex(options) + + '}\\\\{' + this.falseExpr.toTex(options) + + '}, &\\quad{\\text{otherwise}}\\end{cases}' } - - throw new TypeError('Unsupported type of condition "' + typeOf(condition) + '"') } return ConditionalNode diff --git a/src/expression/node/ConstantNode.js b/src/expression/node/ConstantNode.js index df20ed1219..a2d44441d5 100644 --- a/src/expression/node/ConstantNode.js +++ b/src/expression/node/ConstantNode.js @@ -9,170 +9,164 @@ const dependencies = [ ] export const createConstantNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { - /** - * A ConstantNode holds a constant value like a number or string. - * - * Usage: - * - * new ConstantNode(2.3) - * new ConstantNode('hello') - * - * @param {*} value Value can be any type (number, BigNumber, string, ...) - * @constructor ConstantNode - * @extends {Node} - */ - function ConstantNode (value) { - if (!(this instanceof ConstantNode)) { - throw new SyntaxError('Constructor must be called with the new operator') + class ConstantNode extends Node { + /** + * A ConstantNode holds a constant value like a number or string. + * + * Usage: + * + * new ConstantNode(2.3) + * new ConstantNode('hello') + * + * @param {*} value Value can be any type (number, BigNumber, string, ...) + * @constructor ConstantNode + * @extends {Node} + */ + constructor (value) { + super() + this.value = value } - this.value = value - } - - ConstantNode.prototype = new Node() - - ConstantNode.prototype.type = 'ConstantNode' - - ConstantNode.prototype.isConstantNode = true - - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - ConstantNode.prototype._compile = function (math, argNames) { - const value = this.value - - return function evalConstantNode () { - return value + static name = name + get type () { return name } + get isConstantNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + const value = this.value + + return function evalConstantNode () { + return value + } } - } - - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - ConstantNode.prototype.forEach = function (callback) { - // nothing to do, we don't have childs - } - /** - * Create a new ConstantNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node) : Node} callback - * @returns {ConstantNode} Returns a clone of the node - */ - ConstantNode.prototype.map = function (callback) { - return this.clone() - } + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + // nothing to do, we don't have any children + } - /** - * Create a clone of this node, a shallow copy - * @return {ConstantNode} - */ - ConstantNode.prototype.clone = function () { - return new ConstantNode(this.value) - } + /** + * Create a new ConstantNode with children produced by the given callback. + * Trivial because there are no children. + * @param {function(child: Node, path: string, parent: Node) : Node} callback + * @returns {ConstantNode} Returns a clone of the node + */ + map (callback) { + return this.clone() + } - /** - * Get string representation - * @param {Object} options - * @return {string} str - */ - ConstantNode.prototype._toString = function (options) { - return format(this.value, options) - } + /** + * Create a clone of this node, a shallow copy + * @return {ConstantNode} + */ + clone () { + return new ConstantNode(this.value) + } - /** - * Get HTML representation - * @param {Object} options - * @return {string} str - */ - ConstantNode.prototype.toHTML = function (options) { - const value = this._toString(options) - - switch (typeOf(this.value)) { - case 'number': - case 'BigNumber': - case 'Fraction': - return '' + value + '' - case 'string': - return '' + value + '' - case 'boolean': - return '' + value + '' - case 'null': - return '' + value + '' - case 'undefined': - return '' + value + '' - - default: - return '' + value + '' + /** + * Get string representation + * @param {Object} options + * @return {string} str + */ + _toString (options) { + return format(this.value, options) } - } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - ConstantNode.prototype.toJSON = function () { - return { - mathjs: 'ConstantNode', - value: this.value + /** + * Get HTML representation + * @param {Object} options + * @return {string} str + */ + toHTML (options) { + const value = this._toString(options) + + switch (typeOf(this.value)) { + case 'number': + case 'BigNumber': + case 'Fraction': + return '' + value + '' + case 'string': + return '' + value + '' + case 'boolean': + return '' + value + '' + case 'null': + return '' + value + '' + case 'undefined': + return '' + value + '' + + default: + return '' + value + '' + } } - } - /** - * Instantiate a ConstantNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "SymbolNode", value: 2.3}`, - * where mathjs is optional - * @returns {ConstantNode} - */ - ConstantNode.fromJSON = function (json) { - return new ConstantNode(json.value) - } + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { mathjs: name, value: this.value } + } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} str - */ - ConstantNode.prototype._toTex = function (options) { - const value = this._toString(options) - - switch (typeOf(this.value)) { - case 'string': - return '\\mathtt{' + escapeLatex(value) + '}' - - case 'number': - case 'BigNumber': - { - if (!isFinite(this.value)) { - return (this.value.valueOf() < 0) - ? '-\\infty' - : '\\infty' - } + /** + * Instantiate a ConstantNode from its JSON representation + * @param {Object} json An object structured like + * `{"mathjs": "SymbolNode", value: 2.3}`, + * where mathjs is optional + * @returns {ConstantNode} + */ + static fromJSON (json) { + return new ConstantNode(json.value) + } - const index = value.toLowerCase().indexOf('e') - if (index !== -1) { - return value.substring(0, index) + '\\cdot10^{' + + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} str + */ + _toTex (options) { + const value = this._toString(options) + + switch (typeOf(this.value)) { + case 'string': + return '\\mathtt{' + escapeLatex(value) + '}' + + case 'number': + case 'BigNumber': + { + if (!isFinite(this.value)) { + return (this.value.valueOf() < 0) + ? '-\\infty' + : '\\infty' + } + + const index = value.toLowerCase().indexOf('e') + if (index !== -1) { + return value.substring(0, index) + '\\cdot10^{' + value.substring(index + 1) + '}' + } } - } - return value - case 'Fraction': - return this.value.toLatex() + return value + case 'Fraction': + return this.value.toLatex() - default: - return value + default: + return value + } } } diff --git a/src/expression/node/FunctionAssignmentNode.js b/src/expression/node/FunctionAssignmentNode.js index 07b280b9ce..4adce75daf 100644 --- a/src/expression/node/FunctionAssignmentNode.js +++ b/src/expression/node/FunctionAssignmentNode.js @@ -14,210 +14,229 @@ const dependencies = [ ] export const createFunctionAssignmentNode = /* #__PURE__ */ factory(name, dependencies, ({ typed, Node }) => { - /** - * @constructor FunctionAssignmentNode - * @extends {Node} - * Function assignment - * - * @param {string} name Function name - * @param {string[] | Array.<{name: string, type: string}>} params - * Array with function parameter names, or an - * array with objects containing the name - * and type of the parameter - * @param {Node} expr The function expression - */ - function FunctionAssignmentNode (name, params, expr) { - if (!(this instanceof FunctionAssignmentNode)) { - throw new SyntaxError('Constructor must be called with the new operator') - } - - // validate input - if (typeof name !== 'string') throw new TypeError('String expected for parameter "name"') - if (!Array.isArray(params)) throw new TypeError('Array containing strings or objects expected for parameter "params"') - if (!isNode(expr)) throw new TypeError('Node expected for parameter "expr"') - if (keywords.has(name)) throw new Error('Illegal function name, "' + name + '" is a reserved keyword') - - this.name = name - this.params = params.map(function (param) { - return (param && param.name) || param - }) - this.types = params.map(function (param) { - return (param && param.type) || 'any' - }) - this.expr = expr - } - - FunctionAssignmentNode.prototype = new Node() - - FunctionAssignmentNode.prototype.type = 'FunctionAssignmentNode' - - FunctionAssignmentNode.prototype.isFunctionAssignmentNode = true - - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - FunctionAssignmentNode.prototype._compile = function (math, argNames) { - const childArgNames = Object.create(argNames) - forEach(this.params, function (param) { - childArgNames[param] = true - }) - - // compile the function expression with the child args - const evalExpr = this.expr._compile(math, childArgNames) - const name = this.name - const params = this.params - const signature = join(this.types, ',') - const syntax = name + '(' + join(this.params, ', ') + ')' - - return function evalFunctionAssignmentNode (scope, args, context) { - const signatures = {} - signatures[signature] = function () { - const childArgs = Object.create(args) - - for (let i = 0; i < params.length; i++) { - childArgs[params[i]] = arguments[i] - } - - return evalExpr(scope, childArgs, context) - } - const fn = typed(name, signatures) - fn.syntax = syntax - - scope.set(name, fn) - - return fn - } - } - - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - FunctionAssignmentNode.prototype.forEach = function (callback) { - callback(this.expr, 'expr', this) - } - - /** - * Create a new FunctionAssignmentNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {FunctionAssignmentNode} Returns a transformed copy of the node - */ - FunctionAssignmentNode.prototype.map = function (callback) { - const expr = this._ifNode(callback(this.expr, 'expr', this)) - - return new FunctionAssignmentNode(this.name, this.params.slice(0), expr) - } - - /** - * Create a clone of this node, a shallow copy - * @return {FunctionAssignmentNode} - */ - FunctionAssignmentNode.prototype.clone = function () { - return new FunctionAssignmentNode(this.name, this.params.slice(0), this.expr) - } - /** * Is parenthesis needed? * @param {Node} node * @param {Object} parenthesis + * @param {string} implicit * @private */ - function needParenthesis (node, parenthesis) { - const precedence = getPrecedence(node, parenthesis) - const exprPrecedence = getPrecedence(node.expr, parenthesis) + function needParenthesis (node, parenthesis, implicit) { + const precedence = getPrecedence(node, parenthesis, implicit) + const exprPrecedence = getPrecedence(node.expr, parenthesis, implicit) return (parenthesis === 'all') || ((exprPrecedence !== null) && (exprPrecedence <= precedence)) } - /** - * get string representation - * @param {Object} options - * @return {string} str - */ - FunctionAssignmentNode.prototype._toString = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - let expr = this.expr.toString(options) - if (needParenthesis(this, parenthesis)) { - expr = '(' + expr + ')' + class FunctionAssignmentNode extends Node { + /** + * @constructor FunctionAssignmentNode + * @extends {Node} + * Function assignment + * + * @param {string} name Function name + * @param {string[] | Array.<{name: string, type: string}>} params + * Array with function parameter names, or an + * array with objects containing the name + * and type of the parameter + * @param {Node} expr The function expression + */ + constructor (name, params, expr) { + super() + // validate input + if (typeof name !== 'string') { throw new TypeError('String expected for parameter "name"') } + if (!Array.isArray(params)) { + throw new TypeError( + 'Array containing strings or objects expected for parameter "params"') + } + if (!isNode(expr)) { throw new TypeError('Node expected for parameter "expr"') } + if (keywords.has(name)) { throw new Error('Illegal function name, "' + name + '" is a reserved keyword') } + + this.name = name + this.params = params.map(function (param) { + return (param && param.name) || param + }) + this.types = params.map(function (param) { + return (param && param.type) || 'any' + }) + this.expr = expr } - return this.name + '(' + this.params.join(', ') + ') = ' + expr - } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - FunctionAssignmentNode.prototype.toJSON = function () { - const types = this.types - - return { - mathjs: 'FunctionAssignmentNode', - name: this.name, - params: this.params.map(function (param, index) { - return { - name: param, - type: types[index] + static name = name + get type () { return name } + get isFunctionAssignmentNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + const childArgNames = Object.create(argNames) + forEach(this.params, function (param) { + childArgNames[param] = true + }) + + // compile the function expression with the child args + const evalExpr = this.expr._compile(math, childArgNames) + const name = this.name + const params = this.params + const signature = join(this.types, ',') + const syntax = name + '(' + join(this.params, ', ') + ')' + + return function evalFunctionAssignmentNode (scope, args, context) { + const signatures = {} + signatures[signature] = function () { + const childArgs = Object.create(args) + + for (let i = 0; i < params.length; i++) { + childArgs[params[i]] = arguments[i] + } + + return evalExpr(scope, childArgs, context) } - }), - expr: this.expr + const fn = typed(name, signatures) + fn.syntax = syntax + + scope.set(name, fn) + + return fn + } } - } - /** - * Instantiate an FunctionAssignmentNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "FunctionAssignmentNode", name: ..., params: ..., expr: ...}`, - * where mathjs is optional - * @returns {FunctionAssignmentNode} - */ - FunctionAssignmentNode.fromJSON = function (json) { - return new FunctionAssignmentNode(json.name, json.params, json.expr) - } + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + callback(this.expr, 'expr', this) + } - /** - * get HTML representation - * @param {Object} options - * @return {string} str - */ - FunctionAssignmentNode.prototype.toHTML = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - const params = [] - for (let i = 0; i < this.params.length; i++) { - params.push('' + escape(this.params[i]) + '') + /** + * Create a new FunctionAssignmentNode whose children are the results of + * calling the provided callback function for each child of the original + * node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {FunctionAssignmentNode} Returns a transformed copy of the node + */ + map (callback) { + const expr = this._ifNode(callback(this.expr, 'expr', this)) + + return new FunctionAssignmentNode(this.name, this.params.slice(0), expr) + } + + /** + * Create a clone of this node, a shallow copy + * @return {FunctionAssignmentNode} + */ + clone () { + return new FunctionAssignmentNode( + this.name, this.params.slice(0), this.expr) } - let expr = this.expr.toHTML(options) - if (needParenthesis(this, parenthesis)) { - expr = '(' + expr + ')' + + /** + * get string representation + * @param {Object} options + * @return {string} str + */ + _toString (options) { + const parenthesis = + (options && options.parenthesis) ? options.parenthesis : 'keep' + let expr = this.expr.toString(options) + if (needParenthesis(this, parenthesis, options && options.implicit)) { + expr = '(' + expr + ')' + } + return this.name + '(' + this.params.join(', ') + ') = ' + expr } - return '' + escape(this.name) + '' + '(' + params.join(',') + ')=' + expr - } - /** - * get LaTeX representation - * @param {Object} options - * @return {string} str - */ - FunctionAssignmentNode.prototype._toTex = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - let expr = this.expr.toTex(options) - if (needParenthesis(this, parenthesis)) { - expr = `\\left(${expr}\\right)` + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + const types = this.types + + return { + mathjs: name, + name: this.name, + params: this.params.map(function (param, index) { + return { + name: param, + type: types[index] + } + }), + expr: this.expr + } + } + + /** + * Instantiate an FunctionAssignmentNode from its JSON representation + * @param {Object} json + * An object structured like + * ``` + * {"mathjs": "FunctionAssignmentNode", + * name: ..., params: ..., expr: ...} + * ``` + * where mathjs is optional + * @returns {FunctionAssignmentNode} + */ + static fromJSON (json) { + return new FunctionAssignmentNode(json.name, json.params, json.expr) + } + + /** + * get HTML representation + * @param {Object} options + * @return {string} str + */ + toHTML (options) { + const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' + const params = [] + for (let i = 0; i < this.params.length; i++) { + params.push('' + + escape(this.params[i]) + '') + } + let expr = this.expr.toHTML(options) + if (needParenthesis(this, parenthesis, options && options.implicit)) { + expr = '(' + + expr + + ')' + } + return '' + + escape(this.name) + '' + + '(' + + params.join(',') + + ')' + + '=' + + expr } - return '\\mathrm{' + this.name + + /** + * get LaTeX representation + * @param {Object} options + * @return {string} str + */ + _toTex (options) { + const parenthesis = + (options && options.parenthesis) ? options.parenthesis : 'keep' + let expr = this.expr.toTex(options) + if (needParenthesis(this, parenthesis, options && options.implicit)) { + expr = `\\left(${expr}\\right)` + } + + return '\\mathrm{' + this.name + '}\\left(' + this.params.map(toSymbol).join(',') + '\\right):=' + expr + } } return FunctionAssignmentNode diff --git a/src/expression/node/FunctionNode.js b/src/expression/node/FunctionNode.js index 1383a14ec8..c873bff1c7 100644 --- a/src/expression/node/FunctionNode.js +++ b/src/expression/node/FunctionNode.js @@ -14,332 +14,9 @@ const dependencies = [ ] export const createFunctionNode = /* #__PURE__ */ factory(name, dependencies, ({ math, Node, SymbolNode }) => { - /** - * @constructor FunctionNode - * @extends {./Node} - * invoke a list with arguments on a node - * @param {./Node | string} fn Node resolving with a function on which to invoke - * the arguments, typically a SymboNode or AccessorNode - * @param {./Node[]} args - */ - function FunctionNode (fn, args) { - if (!(this instanceof FunctionNode)) { - throw new SyntaxError('Constructor must be called with the new operator') - } - - if (typeof fn === 'string') { - fn = new SymbolNode(fn) - } - - // validate input - if (!isNode(fn)) throw new TypeError('Node expected as parameter "fn"') - if (!Array.isArray(args) || !args.every(isNode)) { - throw new TypeError('Array containing Nodes expected for parameter "args"') - } - - this.fn = fn - this.args = args || [] - - // readonly property name - Object.defineProperty(this, 'name', { - get: function () { - return this.fn.name || '' - }.bind(this), - set: function () { - throw new Error('Cannot assign a new name, name is read-only') - } - }) - } - - FunctionNode.prototype = new Node() - - FunctionNode.prototype.type = 'FunctionNode' - - FunctionNode.prototype.isFunctionNode = true - /* format to fixed length */ const strin = entity => format(entity, { truncate: 78 }) - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - FunctionNode.prototype._compile = function (math, argNames) { - if (!(this instanceof FunctionNode)) { - throw new TypeError('No valid FunctionNode') - } - - // compile arguments - const evalArgs = this.args.map((arg) => arg._compile(math, argNames)) - - if (isSymbolNode(this.fn)) { - const name = this.fn.name - if (!argNames[name]) { - // we can statically determine whether the function has an rawArgs property - const fn = name in math ? getSafeProperty(math, name) : undefined - const isRaw = typeof fn === 'function' && fn.rawArgs === true - - const resolveFn = (scope) => { - let value - if (scope.has(name)) { - value = scope.get(name) - } else if (name in math) { - value = getSafeProperty(math, name) - } else { - return FunctionNode.onUndefinedFunction(name) - } - if (typeof value === 'function') { - return value - } - throw new TypeError( - `'${name}' is not a function; its value is:\n ${strin(value)}` - ) - } - - if (isRaw) { - // pass unevaluated parameters (nodes) to the function - // "raw" evaluation - const rawArgs = this.args - return function evalFunctionNode (scope, args, context) { - const fn = resolveFn(scope) - return fn(rawArgs, math, createSubScope(scope, args), scope) - } - } else { - // "regular" evaluation - switch (evalArgs.length) { - case 0: return function evalFunctionNode (scope, args, context) { - const fn = resolveFn(scope) - return fn() - } - case 1: return function evalFunctionNode (scope, args, context) { - const fn = resolveFn(scope) - const evalArg0 = evalArgs[0] - return fn( - evalArg0(scope, args, context) - ) - } - case 2: return function evalFunctionNode (scope, args, context) { - const fn = resolveFn(scope) - const evalArg0 = evalArgs[0] - const evalArg1 = evalArgs[1] - return fn( - evalArg0(scope, args, context), - evalArg1(scope, args, context) - ) - } - default: return function evalFunctionNode (scope, args, context) { - const fn = resolveFn(scope) - const values = evalArgs.map((evalArg) => evalArg(scope, args, context)) - return fn(...values) - } - } - } - } else { // the function symbol is an argName - const rawArgs = this.args - return function evalFunctionNode (scope, args, context) { - const fn = args[name] - if (typeof fn !== 'function') { - throw new TypeError( - `Argument '${name}' was not a function; received: ${strin(fn)}` - ) - } - if (fn.rawArgs) { - return fn(rawArgs, math, createSubScope(scope, args), scope) // "raw" evaluation - } else { - const values = evalArgs.map((evalArg) => evalArg(scope, args, context)) - return fn.apply(fn, values) - } - } - } - } else if ( - isAccessorNode(this.fn) && - isIndexNode(this.fn.index) && - this.fn.index.isObjectProperty() - ) { - // execute the function with the right context: the object of the AccessorNode - - const evalObject = this.fn.object._compile(math, argNames) - const prop = this.fn.index.getObjectProperty() - const rawArgs = this.args - - return function evalFunctionNode (scope, args, context) { - const object = evalObject(scope, args, context) - validateSafeMethod(object, prop) - const isRaw = object[prop] && object[prop].rawArgs - - if (isRaw) { - return object[prop](rawArgs, math, createSubScope(scope, args), scope) // "raw" evaluation - } else { - // "regular" evaluation - const values = evalArgs.map((evalArg) => evalArg(scope, args, context)) - return object[prop].apply(object, values) - } - } - } else { - // node.fn.isAccessorNode && !node.fn.index.isObjectProperty() - // we have to dynamically determine whether the function has a rawArgs property - const fnExpr = this.fn.toString() - const evalFn = this.fn._compile(math, argNames) - const rawArgs = this.args - - return function evalFunctionNode (scope, args, context) { - const fn = evalFn(scope, args, context) - if (typeof fn !== 'function') { - throw new TypeError( - `Expression '${fnExpr}' did not evaluate to a function; value is:` + - `\n ${strin(fn)}` - ) - } - if (fn.rawArgs) { - return fn(rawArgs, math, createSubScope(scope, args), scope) // "raw" evaluation - } else { - // "regular" evaluation - const values = evalArgs.map((evalArg) => evalArg(scope, args, context)) - return fn.apply(fn, values) - } - } - } - } - - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - FunctionNode.prototype.forEach = function (callback) { - callback(this.fn, 'fn', this) - - for (let i = 0; i < this.args.length; i++) { - callback(this.args[i], 'args[' + i + ']', this) - } - } - - /** - * Create a new FunctionNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {FunctionNode} Returns a transformed copy of the node - */ - FunctionNode.prototype.map = function (callback) { - const fn = this._ifNode(callback(this.fn, 'fn', this)) - const args = [] - for (let i = 0; i < this.args.length; i++) { - args[i] = this._ifNode(callback(this.args[i], 'args[' + i + ']', this)) - } - return new FunctionNode(fn, args) - } - - /** - * Create a clone of this node, a shallow copy - * @return {FunctionNode} - */ - FunctionNode.prototype.clone = function () { - return new FunctionNode(this.fn, this.args.slice(0)) - } - - /** - * Throws an error 'Undefined function {name}' - * @param {string} name - */ - FunctionNode.onUndefinedFunction = function (name) { - throw new Error('Undefined function ' + name) - } - - // backup Node's toString function - // @private - const nodeToString = FunctionNode.prototype.toString - - /** - * Get string representation. (wrapper function) - * This overrides parts of Node's toString function. - * If callback is an object containing callbacks, it - * calls the correct callback for the current node, - * otherwise it falls back to calling Node's toString - * function. - * - * @param {Object} options - * @return {string} str - * @override - */ - FunctionNode.prototype.toString = function (options) { - let customString - const name = this.fn.toString(options) - if (options && (typeof options.handler === 'object') && hasOwnProperty(options.handler, name)) { - // callback is a map of callback functions - customString = options.handler[name](this, options) - } - - if (typeof customString !== 'undefined') { - return customString - } - - // fall back to Node's toString - return nodeToString.call(this, options) - } - - /** - * Get string representation - * @param {Object} options - * @return {string} str - */ - FunctionNode.prototype._toString = function (options) { - const args = this.args.map(function (arg) { - return arg.toString(options) - }) - - const fn = isFunctionAssignmentNode(this.fn) - ? ('(' + this.fn.toString(options) + ')') - : this.fn.toString(options) - - // format the arguments like "add(2, 4.2)" - return fn + '(' + args.join(', ') + ')' - } - - /** - * Get a JSON representation of the node - * @returns {Object} - */ - FunctionNode.prototype.toJSON = function () { - return { - mathjs: 'FunctionNode', - fn: this.fn, - args: this.args - } - } - - /** - * Instantiate an AssignmentNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "FunctionNode", fn: ..., args: ...}`, - * where mathjs is optional - * @returns {FunctionNode} - */ - FunctionNode.fromJSON = function (json) { - return new FunctionNode(json.fn, json.args) - } - - /** - * Get HTML representation - * @param {Object} options - * @return {string} str - */ - FunctionNode.prototype.toHTML = function (options) { - const args = this.args.map(function (arg) { - return arg.toHTML(options) - }) - - // format the arguments like "add(2, 4.2)" - return '' + escape(this.fn) + '(' + args.join(',') + ')' - } - /* * Expand a LaTeX template * @@ -408,94 +85,420 @@ export const createFunctionNode = /* #__PURE__ */ factory(name, dependencies, ({ return latex } - // backup Node's toTex function - // @private - const nodeToTex = FunctionNode.prototype.toTex - - /** - * Get LaTeX representation. (wrapper function) - * This overrides parts of Node's toTex function. - * If callback is an object containing callbacks, it - * calls the correct callback for the current node, - * otherwise it falls back to calling Node's toTex - * function. - * - * @param {Object} options - * @return {string} - */ - FunctionNode.prototype.toTex = function (options) { - let customTex - if (options && (typeof options.handler === 'object') && hasOwnProperty(options.handler, this.name)) { - // callback is a map of callback functions - customTex = options.handler[this.name](this, options) + class FunctionNode extends Node { + /** + * @constructor FunctionNode + * @extends {./Node} + * invoke a list with arguments on a node + * @param {./Node | string} fn + * Item resolving to a function on which to invoke + * the arguments, typically a SymboNode or AccessorNode + * @param {./Node[]} args + */ + constructor (fn, args) { + super() + if (typeof fn === 'string') { + fn = new SymbolNode(fn) + } + + // validate input + if (!isNode(fn)) throw new TypeError('Node expected as parameter "fn"') + if (!Array.isArray(args) || !args.every(isNode)) { + throw new TypeError( + 'Array containing Nodes expected for parameter "args"') + } + + this.fn = fn + this.args = args || [] } - if (typeof customTex !== 'undefined') { - return customTex + // readonly property name + get name () { + return this.fn.name || '' } - // fall back to Node's toTex - return nodeToTex.call(this, options) - } + static name = name + get type () { return name } + get isFunctionNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + // compile arguments + const evalArgs = this.args.map((arg) => arg._compile(math, argNames)) + + if (isSymbolNode(this.fn)) { + const name = this.fn.name + if (!argNames[name]) { + // we can statically determine whether the function + // has the rawArgs property + const fn = name in math ? getSafeProperty(math, name) : undefined + const isRaw = typeof fn === 'function' && fn.rawArgs === true + + const resolveFn = (scope) => { + let value + if (scope.has(name)) { + value = scope.get(name) + } else if (name in math) { + value = getSafeProperty(math, name) + } else { + return FunctionNode.onUndefinedFunction(name) + } + if (typeof value === 'function') { + return value + } + throw new TypeError( + `'${name}' is not a function; its value is:\n ${strin(value)}` + ) + } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} str - */ - FunctionNode.prototype._toTex = function (options) { - const args = this.args.map(function (arg) { // get LaTeX of the arguments - return arg.toTex(options) - }) + if (isRaw) { + // pass unevaluated parameters (nodes) to the function + // "raw" evaluation + const rawArgs = this.args + return function evalFunctionNode (scope, args, context) { + const fn = resolveFn(scope) + return fn(rawArgs, math, createSubScope(scope, args), scope) + } + } else { + // "regular" evaluation + switch (evalArgs.length) { + case 0: return function evalFunctionNode (scope, args, context) { + const fn = resolveFn(scope) + return fn() + } + case 1: return function evalFunctionNode (scope, args, context) { + const fn = resolveFn(scope) + const evalArg0 = evalArgs[0] + return fn( + evalArg0(scope, args, context) + ) + } + case 2: return function evalFunctionNode (scope, args, context) { + const fn = resolveFn(scope) + const evalArg0 = evalArgs[0] + const evalArg1 = evalArgs[1] + return fn( + evalArg0(scope, args, context), + evalArg1(scope, args, context) + ) + } + default: return function evalFunctionNode (scope, args, context) { + const fn = resolveFn(scope) + const values = evalArgs.map( + (evalArg) => evalArg(scope, args, context)) + return fn(...values) + } + } + } + } else { // the function symbol is an argName + const rawArgs = this.args + return function evalFunctionNode (scope, args, context) { + const fn = args[name] + if (typeof fn !== 'function') { + throw new TypeError( + `Argument '${name}' was not a function; received: ${strin(fn)}` + ) + } + if (fn.rawArgs) { + // "Raw" evaluation + return fn(rawArgs, math, createSubScope(scope, args), scope) + } else { + const values = evalArgs.map( + (evalArg) => evalArg(scope, args, context)) + return fn.apply(fn, values) + } + } + } + } else if ( + isAccessorNode(this.fn) && + isIndexNode(this.fn.index) && + this.fn.index.isObjectProperty() + ) { + // execute the function with the right context: + // the object of the AccessorNode + + const evalObject = this.fn.object._compile(math, argNames) + const prop = this.fn.index.getObjectProperty() + const rawArgs = this.args - let latexConverter + return function evalFunctionNode (scope, args, context) { + const object = evalObject(scope, args, context) + validateSafeMethod(object, prop) + const isRaw = object[prop] && object[prop].rawArgs + + if (isRaw) { + // "Raw" evaluation + return object[prop]( + rawArgs, math, createSubScope(scope, args), scope) + } else { + // "regular" evaluation + const values = evalArgs.map( + (evalArg) => evalArg(scope, args, context)) + return object[prop].apply(object, values) + } + } + } else { + // node.fn.isAccessorNode && !node.fn.index.isObjectProperty() + // we have to dynamically determine whether the function has the + // rawArgs property + const fnExpr = this.fn.toString() + const evalFn = this.fn._compile(math, argNames) + const rawArgs = this.args - if (latexFunctions[this.name]) { - latexConverter = latexFunctions[this.name] + return function evalFunctionNode (scope, args, context) { + const fn = evalFn(scope, args, context) + if (typeof fn !== 'function') { + throw new TypeError( + `Expression '${fnExpr}' did not evaluate to a function; value is:` + + `\n ${strin(fn)}` + ) + } + if (fn.rawArgs) { + // "Raw" evaluation + return fn(rawArgs, math, createSubScope(scope, args), scope) + } else { + // "regular" evaluation + const values = evalArgs.map( + (evalArg) => evalArg(scope, args, context)) + return fn.apply(fn, values) + } + } + } } - // toTex property on the function itself - if (math[this.name] && - ((typeof math[this.name].toTex === 'function') || - (typeof math[this.name].toTex === 'object') || - (typeof math[this.name].toTex === 'string')) - ) { - // .toTex is a callback function - latexConverter = math[this.name].toTex + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + callback(this.fn, 'fn', this) + + for (let i = 0; i < this.args.length; i++) { + callback(this.args[i], 'args[' + i + ']', this) + } } - let customToTex - switch (typeof latexConverter) { - case 'function': // a callback function - customToTex = latexConverter(this, options) - break - case 'string': // a template string - customToTex = expandTemplate(latexConverter, this, options) - break - case 'object': // an object with different "converters" for different numbers of arguments - switch (typeof latexConverter[args.length]) { - case 'function': - customToTex = latexConverter[args.length](this, options) - break - case 'string': - customToTex = expandTemplate(latexConverter[args.length], this, options) - break - } + /** + * Create a new FunctionNode whose children are the results of calling + * the provided callback function for each child of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {FunctionNode} Returns a transformed copy of the node + */ + map (callback) { + const fn = this._ifNode(callback(this.fn, 'fn', this)) + const args = [] + for (let i = 0; i < this.args.length; i++) { + args[i] = this._ifNode(callback(this.args[i], 'args[' + i + ']', this)) + } + return new FunctionNode(fn, args) } - if (typeof customToTex !== 'undefined') { - return customToTex + /** + * Create a clone of this node, a shallow copy + * @return {FunctionNode} + */ + clone () { + return new FunctionNode(this.fn, this.args.slice(0)) } - return expandTemplate(defaultTemplate, this, options) - } + /** + * Throws an error 'Undefined function {name}' + * @param {string} name + */ + static onUndefinedFunction = function (name) { + throw new Error('Undefined function ' + name) + } + + /** + * Get string representation. (wrapper function) + * This overrides parts of Node's toString function. + * If callback is an object containing callbacks, it + * calls the correct callback for the current node, + * otherwise it falls back to calling Node's toString + * function. + * + * @param {Object} options + * @return {string} str + * @override + */ + toString (options) { + let customString + const name = this.fn.toString(options) + if (options && + (typeof options.handler === 'object') && + hasOwnProperty(options.handler, name)) { + // callback is a map of callback functions + customString = options.handler[name](this, options) + } + + if (typeof customString !== 'undefined') { + return customString + } + + // fall back to Node's toString + return super.toString(options) + } + + /** + * Get string representation + * @param {Object} options + * @return {string} str + */ + _toString (options) { + const args = this.args.map(function (arg) { + return arg.toString(options) + }) + + const fn = isFunctionAssignmentNode(this.fn) + ? ('(' + this.fn.toString(options) + ')') + : this.fn.toString(options) + + // format the arguments like "add(2, 4.2)" + return fn + '(' + args.join(', ') + ')' + } + + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { + mathjs: name, + fn: this.fn, + args: this.args + } + } + + /** + * Instantiate an AssignmentNode from its JSON representation + * @param {Object} json An object structured like + * `{"mathjs": "FunctionNode", fn: ..., args: ...}`, + * where mathjs is optional + * @returns {FunctionNode} + */ + static fromJSON = function (json) { + return new FunctionNode(json.fn, json.args) + } + + /** + * Get HTML representation + * @param {Object} options + * @return {string} str + */ + toHTML (options) { + const args = this.args.map(function (arg) { + return arg.toHTML(options) + }) + + // format the arguments like "add(2, 4.2)" + return '' + escape(this.fn) + + '(' + + args.join(',') + + ')' + } + + /** + * Get LaTeX representation. (wrapper function) + * This overrides parts of Node's toTex function. + * If callback is an object containing callbacks, it + * calls the correct callback for the current node, + * otherwise it falls back to calling Node's toTex + * function. + * + * @param {Object} options + * @return {string} + */ + toTex (options) { + let customTex + if (options && + (typeof options.handler === 'object') && + hasOwnProperty(options.handler, this.name)) { + // callback is a map of callback functions + customTex = options.handler[this.name](this, options) + } + + if (typeof customTex !== 'undefined') { + return customTex + } + + // fall back to Node's toTex + return super.toTex(options) + } + + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} str + */ + _toTex (options) { + const args = this.args.map(function (arg) { // get LaTeX of the arguments + return arg.toTex(options) + }) + + let latexConverter + + if (latexFunctions[this.name]) { + latexConverter = latexFunctions[this.name] + } + + // toTex property on the function itself + if (math[this.name] && + ((typeof math[this.name].toTex === 'function') || + (typeof math[this.name].toTex === 'object') || + (typeof math[this.name].toTex === 'string')) + ) { + // .toTex is a callback function + latexConverter = math[this.name].toTex + } - /** - * Get identifier. - * @return {string} - */ - FunctionNode.prototype.getIdentifier = function () { - return this.type + ':' + this.name + let customToTex + switch (typeof latexConverter) { + case 'function': // a callback function + customToTex = latexConverter(this, options) + break + case 'string': // a template string + customToTex = expandTemplate(latexConverter, this, options) + break + case 'object': + // an object with different "converters" for different + // numbers of arguments + switch (typeof latexConverter[args.length]) { + case 'function': + customToTex = latexConverter[args.length](this, options) + break + case 'string': + customToTex = + expandTemplate(latexConverter[args.length], this, options) + break + } + } + + if (typeof customToTex !== 'undefined') { + return customToTex + } + + return expandTemplate(defaultTemplate, this, options) + } + + /** + * Get identifier. + * @return {string} + */ + getIdentifier () { + return this.type + ':' + this.name + } } return FunctionNode diff --git a/src/expression/node/IndexNode.js b/src/expression/node/IndexNode.js index d39525baae..bf4186bbc1 100644 --- a/src/expression/node/IndexNode.js +++ b/src/expression/node/IndexNode.js @@ -11,225 +11,230 @@ const dependencies = [ ] export const createIndexNode = /* #__PURE__ */ factory(name, dependencies, ({ Node, size }) => { - /** - * @constructor IndexNode - * @extends Node - * - * Describes a subset of a matrix or an object property. - * Cannot be used on its own, needs to be used within an AccessorNode or - * AssignmentNode. - * - * @param {Node[]} dimensions - * @param {boolean} [dotNotation=false] Optional property describing whether - * this index was written using dot - * notation like `a.b`, or using bracket - * notation like `a["b"]` (default). - * Used to stringify an IndexNode. - */ - function IndexNode (dimensions, dotNotation) { - if (!(this instanceof IndexNode)) { - throw new SyntaxError('Constructor must be called with the new operator') + class IndexNode extends Node { + /** + * @constructor IndexNode + * @extends Node + * + * Describes a subset of a matrix or an object property. + * Cannot be used on its own, needs to be used within an AccessorNode or + * AssignmentNode. + * + * @param {Node[]} dimensions + * @param {boolean} [dotNotation=false] + * Optional property describing whether this index was written using dot + * notation like `a.b`, or using bracket notation like `a["b"]` + * (which is the default). This property is used for string conversion. + */ + constructor (dimensions, dotNotation) { + super() + this.dimensions = dimensions + this.dotNotation = dotNotation || false + + // validate input + if (!Array.isArray(dimensions) || !dimensions.every(isNode)) { + throw new TypeError( + 'Array containing Nodes expected for parameter "dimensions"') + } + if (this.dotNotation && !this.isObjectProperty()) { + throw new Error('dotNotation only applicable for object properties') + } } - this.dimensions = dimensions - this.dotNotation = dotNotation || false - - // validate input - if (!Array.isArray(dimensions) || !dimensions.every(isNode)) { - throw new TypeError('Array containing Nodes expected for parameter "dimensions"') - } - if (this.dotNotation && !this.isObjectProperty()) { - throw new Error('dotNotation only applicable for object properties') - } - } - - IndexNode.prototype = new Node() - - IndexNode.prototype.type = 'IndexNode' - - IndexNode.prototype.isIndexNode = true - - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - IndexNode.prototype._compile = function (math, argNames) { - // TODO: implement support for bignumber (currently bignumbers are silently - // reduced to numbers when changing the value to zero-based) - - // TODO: Optimization: when the range values are ConstantNodes, - // we can beforehand resolve the zero-based value - - // optimization for a simple object property - const evalDimensions = map(this.dimensions, function (dimension, i) { - const needsEnd = dimension - .filter(node => node.isSymbolNode && node.name === 'end') - .length > 0 - - if (needsEnd) { - // SymbolNode 'end' is used inside the index, - // like in `A[end]` or `A[end - 2]` - const childArgNames = Object.create(argNames) - childArgNames.end = true - - const _evalDimension = dimension._compile(math, childArgNames) - - return function evalDimension (scope, args, context) { - if (!isMatrix(context) && !isArray(context) && !isString(context)) { - throw new TypeError('Cannot resolve "end": ' + - 'context must be a Matrix, Array, or string but is ' + typeOf(context)) + static name = name + get type () { return name } + get isIndexNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + // TODO: implement support for bignumber (currently bignumbers are silently + // reduced to numbers when changing the value to zero-based) + + // TODO: Optimization: when the range values are ConstantNodes, + // we can beforehand resolve the zero-based value + + // optimization for a simple object property + const evalDimensions = map(this.dimensions, function (dimension, i) { + const needsEnd = dimension + .filter(node => node.isSymbolNode && node.name === 'end') + .length > 0 + + if (needsEnd) { + // SymbolNode 'end' is used inside the index, + // like in `A[end]` or `A[end - 2]` + const childArgNames = Object.create(argNames) + childArgNames.end = true + + const _evalDimension = dimension._compile(math, childArgNames) + + return function evalDimension (scope, args, context) { + if (!isMatrix(context) && !isArray(context) && !isString(context)) { + throw new TypeError( + 'Cannot resolve "end": ' + + 'context must be a Matrix, Array, or string but is ' + + typeOf(context)) + } + + const s = size(context).valueOf() + const childArgs = Object.create(args) + childArgs.end = s[i] + + return _evalDimension(scope, childArgs, context) } - - const s = size(context).valueOf() - const childArgs = Object.create(args) - childArgs.end = s[i] - - return _evalDimension(scope, childArgs, context) + } else { + // SymbolNode `end` not used + return dimension._compile(math, argNames) } - } else { - // SymbolNode `end` not used - return dimension._compile(math, argNames) - } - }) + }) - const index = getSafeProperty(math, 'index') + const index = getSafeProperty(math, 'index') - return function evalIndexNode (scope, args, context) { - const dimensions = map(evalDimensions, function (evalDimension) { - return evalDimension(scope, args, context) - }) + return function evalIndexNode (scope, args, context) { + const dimensions = map(evalDimensions, function (evalDimension) { + return evalDimension(scope, args, context) + }) - return index(...dimensions) + return index(...dimensions) + } } - } - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - IndexNode.prototype.forEach = function (callback) { - for (let i = 0; i < this.dimensions.length; i++) { - callback(this.dimensions[i], 'dimensions[' + i + ']', this) + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + for (let i = 0; i < this.dimensions.length; i++) { + callback(this.dimensions[i], 'dimensions[' + i + ']', this) + } } - } - /** - * Create a new IndexNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {IndexNode} Returns a transformed copy of the node - */ - IndexNode.prototype.map = function (callback) { - const dimensions = [] - for (let i = 0; i < this.dimensions.length; i++) { - dimensions[i] = this._ifNode(callback(this.dimensions[i], 'dimensions[' + i + ']', this)) - } + /** + * Create a new IndexNode whose children are the results of calling + * the provided callback function for each child of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {IndexNode} Returns a transformed copy of the node + */ + map (callback) { + const dimensions = [] + for (let i = 0; i < this.dimensions.length; i++) { + dimensions[i] = this._ifNode( + callback(this.dimensions[i], 'dimensions[' + i + ']', this)) + } - return new IndexNode(dimensions, this.dotNotation) - } + return new IndexNode(dimensions, this.dotNotation) + } - /** - * Create a clone of this node, a shallow copy - * @return {IndexNode} - */ - IndexNode.prototype.clone = function () { - return new IndexNode(this.dimensions.slice(0), this.dotNotation) - } + /** + * Create a clone of this node, a shallow copy + * @return {IndexNode} + */ + clone () { + return new IndexNode(this.dimensions.slice(0), this.dotNotation) + } - /** - * Test whether this IndexNode contains a single property name - * @return {boolean} - */ - IndexNode.prototype.isObjectProperty = function () { - return this.dimensions.length === 1 && + /** + * Test whether this IndexNode contains a single property name + * @return {boolean} + */ + isObjectProperty () { + return this.dimensions.length === 1 && isConstantNode(this.dimensions[0]) && typeof this.dimensions[0].value === 'string' - } - - /** - * Returns the property name if IndexNode contains a property. - * If not, returns null. - * @return {string | null} - */ - IndexNode.prototype.getObjectProperty = function () { - return this.isObjectProperty() ? this.dimensions[0].value : null - } + } - /** - * Get string representation - * @param {Object} options - * @return {string} str - */ - IndexNode.prototype._toString = function (options) { - // format the parameters like "[1, 0:5]" - return this.dotNotation - ? ('.' + this.getObjectProperty()) - : ('[' + this.dimensions.join(', ') + ']') - } + /** + * Returns the property name if IndexNode contains a property. + * If not, returns null. + * @return {string | null} + */ + getObjectProperty () { + return this.isObjectProperty() ? this.dimensions[0].value : null + } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - IndexNode.prototype.toJSON = function () { - return { - mathjs: 'IndexNode', - dimensions: this.dimensions, - dotNotation: this.dotNotation + /** + * Get string representation + * @param {Object} options + * @return {string} str + */ + _toString (options) { + // format the parameters like "[1, 0:5]" + return this.dotNotation + ? ('.' + this.getObjectProperty()) + : ('[' + this.dimensions.join(', ') + ']') } - } - /** - * Instantiate an IndexNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "IndexNode", dimensions: [...], dotNotation: false}`, - * where mathjs is optional - * @returns {IndexNode} - */ - IndexNode.fromJSON = function (json) { - return new IndexNode(json.dimensions, json.dotNotation) - } + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { + mathjs: name, + dimensions: this.dimensions, + dotNotation: this.dotNotation + } + } - /** - * Get HTML representation - * @param {Object} options - * @return {string} str - */ - IndexNode.prototype.toHTML = function (options) { - // format the parameters like "[1, 0:5]" - const dimensions = [] - for (let i = 0; i < this.dimensions.length; i++) { - dimensions[i] = this.dimensions[i].toHTML() + /** + * Instantiate an IndexNode from its JSON representation + * @param {Object} json + * An object structured like + * `{"mathjs": "IndexNode", dimensions: [...], dotNotation: false}`, + * where mathjs is optional + * @returns {IndexNode} + */ + static fromJSON (json) { + return new IndexNode(json.dimensions, json.dotNotation) } - if (this.dotNotation) { - return '.' + '' + escape(this.getObjectProperty()) + '' - } else { - return '[' + dimensions.join(',') + ']' + + /** + * Get HTML representation + * @param {Object} options + * @return {string} str + */ + toHTML (options) { + // format the parameters like "[1, 0:5]" + const dimensions = [] + for (let i = 0; i < this.dimensions.length; i++) { + dimensions[i] = this.dimensions[i].toHTML() + } + if (this.dotNotation) { + return '.' + + '' + + escape(this.getObjectProperty()) + '' + } else { + return '[' + + dimensions.join(',') + + ']' + } } - } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} str - */ - IndexNode.prototype._toTex = function (options) { - const dimensions = this.dimensions.map(function (range) { - return range.toTex(options) - }) - - return this.dotNotation - ? ('.' + this.getObjectProperty() + '') - : ('_{' + dimensions.join(',') + '}') + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} str + */ + _toTex (options) { + const dimensions = this.dimensions.map(function (range) { + return range.toTex(options) + }) + + return this.dotNotation + ? ('.' + this.getObjectProperty() + '') + : ('_{' + dimensions.join(',') + '}') + } } return IndexNode diff --git a/src/expression/node/Node.js b/src/expression/node/Node.js index 55eb06a62b..3ac251313d 100644 --- a/src/expression/node/Node.js +++ b/src/expression/node/Node.js @@ -10,370 +10,362 @@ const dependencies = ['mathWithTransform'] export const createNode = /* #__PURE__ */ factory(name, dependencies, ({ mathWithTransform }) => { /** - * Node + * Validate the symbol names of a scope. + * Throws an error when the scope contains an illegal symbol. + * @param {Object} scope */ - function Node () { - if (!(this instanceof Node)) { - throw new SyntaxError('Constructor must be called with the new operator') + function _validateScope (scope) { + for (const symbol of [...keywords]) { + if (scope.has(symbol)) { + throw new Error('Scope contains an illegal symbol, "' + symbol + '" is a reserved keyword') + } } } - /** - * Evaluate the node - * @param {Object} [scope] Scope to read/write variables - * @return {*} Returns the result - */ - Node.prototype.evaluate = function (scope) { - return this.compile().evaluate(scope) - } - - Node.prototype.type = 'Node' - - Node.prototype.isNode = true + class Node { + get type () { return 'Node' } + get isNode () { return true } + + /** + * Evaluate the node + * @param {Object} [scope] Scope to read/write variables + * @return {*} Returns the result + */ + evaluate (scope) { + return this.compile().evaluate(scope) + } - Node.prototype.comment = '' + /** + * Compile the node into an optimized, evauatable JavaScript function + * @return {{evaluate: function([Object])}} object + * Returns an object with a function 'evaluate', + * which can be invoked as expr.evaluate([scope: Object]), + * where scope is an optional object with + * variables. + */ + compile () { + const expr = this._compile(mathWithTransform, {}) + const args = {} + const context = null + + function evaluate (scope) { + const s = createMap(scope) + _validateScope(s) + return expr(s, args, context) + } - /** - * Compile the node into an optimized, evauatable JavaScript function - * @return {{evaluate: function([Object])}} object - * Returns an object with a function 'evaluate', - * which can be invoked as expr.evaluate([scope: Object]), - * where scope is an optional object with - * variables. - */ - Node.prototype.compile = function () { - const expr = this._compile(mathWithTransform, {}) - const args = {} - const context = null - - function evaluate (scope) { - const s = createMap(scope) - _validateScope(s) - return expr(s, args, context) + return { + evaluate + } } - return { - evaluate + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + throw new Error('Method _compile must be implemented by type ' + this.type) } - } - - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - Node.prototype._compile = function (math, argNames) { - throw new Error('Method _compile should be implemented by type ' + this.type) - } - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - Node.prototype.forEach = function (callback) { - // must be implemented by each of the Node implementations - throw new Error('Cannot run forEach on a Node interface') - } - - /** - * Create a new Node having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {OperatorNode} Returns a transformed copy of the node - */ - Node.prototype.map = function (callback) { - // must be implemented by each of the Node implementations - throw new Error('Cannot run map on a Node interface') - } - - /** - * Validate whether an object is a Node, for use with map - * @param {Node} node - * @returns {Node} Returns the input if it's a node, else throws an Error - * @protected - */ - Node.prototype._ifNode = function (node) { - if (!isNode(node)) { - throw new TypeError('Callback function must return a Node') + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + // must be implemented by each of the Node implementations + throw new Error('Cannot run forEach on a Node interface') } - return node - } - - /** - * Recursively traverse all nodes in a node tree. Executes given callback for - * this node and each of its child nodes. - * @param {function(node: Node, path: string, parent: Node)} callback - * A callback called for every node in the node tree. - */ - Node.prototype.traverse = function (callback) { - // execute callback for itself - // eslint-disable-next-line - callback(this, null, null) - - // recursively traverse over all childs of a node - function _traverse (node, callback) { - node.forEach(function (child, path, parent) { - callback(child, path, parent) - _traverse(child, callback) - }) + /** + * Create a new Node whose children are the results of calling the + * provided callback function for each child of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {OperatorNode} Returns a transformed copy of the node + */ + map (callback) { + // must be implemented by each of the Node implementations + throw new Error('Cannot run map on a Node interface') } - _traverse(this, callback) - } - - /** - * Recursively transform a node tree via a transform function. - * - * For example, to replace all nodes of type SymbolNode having name 'x' with a - * ConstantNode with value 2: - * - * const res = Node.transform(function (node, path, parent) { - * if (node && node.isSymbolNode) && (node.name === 'x')) { - * return new ConstantNode(2) - * } - * else { - * return node - * } - * }) - * - * @param {function(node: Node, path: string, parent: Node) : Node} callback - * A mapping function accepting a node, and returning - * a replacement for the node or the original node. - * Signature: callback(node: Node, index: string, parent: Node) : Node - * @return {Node} Returns the original node or its replacement - */ - Node.prototype.transform = function (callback) { - function _transform (child, path, parent) { - const replacement = callback(child, path, parent) - - if (replacement !== child) { - // stop iterating when the node is replaced - return replacement + /** + * Validate whether an object is a Node, for use with map + * @param {Node} node + * @returns {Node} Returns the input if it's a node, else throws an Error + * @protected + */ + _ifNode (node) { + if (!isNode(node)) { + throw new TypeError('Callback function must return a Node') } - - return child.map(_transform) + return node } - return _transform(this, null, null) - } - - /** - * Find any node in the node tree matching given filter function. For example, to - * find all nodes of type SymbolNode having name 'x': - * - * const results = Node.filter(function (node) { - * return (node && node.isSymbolNode) && (node.name === 'x') - * }) - * - * @param {function(node: Node, path: string, parent: Node) : Node} callback - * A test function returning true when a node matches, and false - * otherwise. Function signature: - * callback(node: Node, index: string, parent: Node) : boolean - * @return {Node[]} nodes An array with nodes matching given filter criteria - */ - Node.prototype.filter = function (callback) { - const nodes = [] - - this.traverse(function (node, path, parent) { - if (callback(node, path, parent)) { - nodes.push(node) + /** + * Recursively traverse all nodes in a node tree. Executes given callback for + * this node and each of its child nodes. + * @param {function(node: Node, path: string, parent: Node)} callback + * A callback called for every node in the node tree. + */ + traverse (callback) { + // execute callback for itself + // eslint-disable-next-line + callback(this, null, null) + + // recursively traverse over all children of a node + function _traverse (node, callback) { + node.forEach(function (child, path, parent) { + callback(child, path, parent) + _traverse(child, callback) + }) } - }) - return nodes - } + _traverse(this, callback) + } - /** - * Create a shallow clone of this node - * @return {Node} - */ - Node.prototype.clone = function () { - // must be implemented by each of the Node implementations - throw new Error('Cannot clone a Node interface') - } + /** + * Recursively transform a node tree via a transform function. + * + * For example, to replace all nodes of type SymbolNode having name 'x' with + * a ConstantNode with value 2: + * + * const res = Node.transform(function (node, path, parent) { + * if (node && node.isSymbolNode) && (node.name === 'x')) { + * return new ConstantNode(2) + * } + * else { + * return node + * } + * }) + * + * @param {function(node: Node, path: string, parent: Node) : Node} callback + * A mapping function accepting a node, and returning + * a replacement for the node or the original node. The "signature" + * of the callback must be: + * callback(node: Node, index: string, parent: Node) : Node + * @return {Node} Returns the original node or its replacement + */ + transform (callback) { + function _transform (child, path, parent) { + const replacement = callback(child, path, parent) + + if (replacement !== child) { + // stop iterating when the node is replaced + return replacement + } + + return child.map(_transform) + } - /** - * Create a deep clone of this node - * @return {Node} - */ - Node.prototype.cloneDeep = function () { - return this.map(function (node) { - return node.cloneDeep() - }) - } + return _transform(this, null, null) + } - /** - * Deep compare this node with another node. - * @param {Node} other - * @return {boolean} Returns true when both nodes are of the same type and - * contain the same values (as do their childs) - */ - Node.prototype.equals = function (other) { - return other - ? deepStrictEqual(this, other) - : false - } + /** + * Find any node in the node tree matching given filter function. For + * example, to find all nodes of type SymbolNode having name 'x': + * + * const results = Node.filter(function (node) { + * return (node && node.isSymbolNode) && (node.name === 'x') + * }) + * + * @param {function(node: Node, path: string, parent: Node) : Node} callback + * A test function returning true when a node matches, and false + * otherwise. Function signature: + * callback(node: Node, index: string, parent: Node) : boolean + * @return {Node[]} nodes + * An array with nodes matching given filter criteria + */ + filter (callback) { + const nodes = [] + + this.traverse(function (node, path, parent) { + if (callback(node, path, parent)) { + nodes.push(node) + } + }) - /** - * Get string representation. (wrapper function) - * - * This function can get an object of the following form: - * { - * handler: //This can be a callback function of the form - * // "function callback(node, options)"or - * // a map that maps function names (used in FunctionNodes) - * // to callbacks - * parenthesis: "keep" //the parenthesis option (This is optional) - * } - * - * @param {Object} [options] - * @return {string} - */ - Node.prototype.toString = function (options) { - const customString = this._getCustomString(options) + return nodes + } - if (typeof customString !== 'undefined') { - return customString + /** + * Create a shallow clone of this node + * @return {Node} + */ + clone () { + // must be implemented by each of the Node implementations + throw new Error('Cannot clone a Node interface') } - return this._toString(options) - } + /** + * Create a deep clone of this node + * @return {Node} + */ + cloneDeep () { + return this.map(function (node) { + return node.cloneDeep() + }) + } - /** - * Get a JSON representation of the node - * Both .toJSON() and the static .fromJSON(json) should be implemented by all - * implementations of Node - * @returns {Object} - */ - Node.prototype.toJSON = function () { - throw new Error('Cannot serialize object: toJSON not implemented by ' + this.type) - } + /** + * Deep compare this node with another node. + * @param {Node} other + * @return {boolean} Returns true when both nodes are of the same type and + * contain the same values (as do their childs) + */ + equals (other) { + return other + ? this.type === other.type && deepStrictEqual(this, other) + : false + } - /** - * Get HTML representation. (wrapper function) - * - * This function can get an object of the following form: - * { - * handler: //This can be a callback function of the form - * // "function callback(node, options)" or - * // a map that maps function names (used in FunctionNodes) - * // to callbacks - * parenthesis: "keep" //the parenthesis option (This is optional) - * } - * - * @param {Object} [options] - * @return {string} - */ - Node.prototype.toHTML = function (options) { - const customString = this._getCustomString(options) + /** + * Get string representation. (wrapper function) + * + * This function can get an object of the following form: + * { + * handler: //This can be a callback function of the form + * // "function callback(node, options)"or + * // a map that maps function names (used in FunctionNodes) + * // to callbacks + * parenthesis: "keep" //the parenthesis option (This is optional) + * } + * + * @param {Object} [options] + * @return {string} + */ + toString (options) { + const customString = this._getCustomString(options) + + if (typeof customString !== 'undefined') { + return customString + } - if (typeof customString !== 'undefined') { - return customString + return this._toString(options) } - return this.toHTML(options) - } + /** + * Get a JSON representation of the node + * Both .toJSON() and the static .fromJSON(json) should be implemented by all + * implementations of Node + * @returns {Object} + */ + toJSON () { + throw new Error( + 'Cannot serialize object: toJSON not implemented by ' + this.type) + } - /** - * Internal function to generate the string output. - * This has to be implemented by every Node - * - * @throws {Error} - */ - Node.prototype._toString = function () { - // must be implemented by each of the Node implementations - throw new Error('_toString not implemented for ' + this.type) - } + /** + * Get HTML representation. (wrapper function) + * + * This function can get an object of the following form: + * { + * handler: //This can be a callback function of the form + * // "function callback(node, options)" or + * // a map that maps function names (used in FunctionNodes) + * // to callbacks + * parenthesis: "keep" //the parenthesis option (This is optional) + * } + * + * @param {Object} [options] + * @return {string} + */ + toHTML (options) { + const customString = this._getCustomString(options) + + if (typeof customString !== 'undefined') { + return customString + } - /** - * Get LaTeX representation. (wrapper function) - * - * This function can get an object of the following form: - * { - * handler: //This can be a callback function of the form - * // "function callback(node, options)"or - * // a map that maps function names (used in FunctionNodes) - * // to callbacks - * parenthesis: "keep" //the parenthesis option (This is optional) - * } - * - * @param {Object} [options] - * @return {string} - */ - Node.prototype.toTex = function (options) { - const customString = this._getCustomString(options) + return this.toHTML(options) + } - if (typeof customString !== 'undefined') { - return customString + /** + * Internal function to generate the string output. + * This has to be implemented by every Node + * + * @throws {Error} + */ + _toString () { + // must be implemented by each of the Node implementations + throw new Error('_toString not implemented for ' + this.type) } - return this._toTex(options) - } + /** + * Get LaTeX representation. (wrapper function) + * + * This function can get an object of the following form: + * { + * handler: //This can be a callback function of the form + * // "function callback(node, options)"or + * // a map that maps function names (used in FunctionNodes) + * // to callbacks + * parenthesis: "keep" //the parenthesis option (This is optional) + * } + * + * @param {Object} [options] + * @return {string} + */ + toTex (options) { + const customString = this._getCustomString(options) + + if (typeof customString !== 'undefined') { + return customString + } - /** - * Internal function to generate the LaTeX output. - * This has to be implemented by every Node - * - * @param {Object} [options] - * @throws {Error} - */ - Node.prototype._toTex = function (options) { - // must be implemented by each of the Node implementations - throw new Error('_toTex not implemented for ' + this.type) - } + return this._toTex(options) + } - /** - * Helper used by `to...` functions. - */ - Node.prototype._getCustomString = function (options) { - if (options && typeof options === 'object') { - switch (typeof options.handler) { - case 'object': - case 'undefined': - return - case 'function': - return options.handler(this, options) - default: - throw new TypeError('Object or function expected as callback') - } + /** + * Internal function to generate the LaTeX output. + * This has to be implemented by every Node + * + * @param {Object} [options] + * @throws {Error} + */ + _toTex (options) { + // must be implemented by each of the Node implementations + throw new Error('_toTex not implemented for ' + this.type) } - } - /** - * Get identifier. - * @return {string} - */ - Node.prototype.getIdentifier = function () { - return this.type - } + /** + * Helper used by `to...` functions. + */ + _getCustomString (options) { + if (options && typeof options === 'object') { + switch (typeof options.handler) { + case 'object': + case 'undefined': + return + case 'function': + return options.handler(this, options) + default: + throw new TypeError('Object or function expected as callback') + } + } + } - /** - * Get the content of the current Node. - * @return {Node} node - **/ - Node.prototype.getContent = function () { - return this - } + /** + * Get identifier. + * @return {string} + */ + getIdentifier () { + return this.type + } - /** - * Validate the symbol names of a scope. - * Throws an error when the scope contains an illegal symbol. - * @param {Object} scope - */ - function _validateScope (scope) { - for (const symbol of [...keywords]) { - if (scope.has(symbol)) { - throw new Error('Scope contains an illegal symbol, "' + symbol + '" is a reserved keyword') - } + /** + * Get the content of the current Node. + * @return {Node} node + **/ + getContent () { + return this } } diff --git a/src/expression/node/ObjectNode.js b/src/expression/node/ObjectNode.js index 5d6168252e..99c8b5b0dc 100644 --- a/src/expression/node/ObjectNode.js +++ b/src/expression/node/ObjectNode.js @@ -10,188 +10,199 @@ const dependencies = [ ] export const createObjectNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { - /** - * @constructor ObjectNode - * @extends {Node} - * Holds an object with keys/values - * @param {Object.} [properties] object with key/value pairs - */ - function ObjectNode (properties) { - if (!(this instanceof ObjectNode)) { - throw new SyntaxError('Constructor must be called with the new operator') + class ObjectNode extends Node { + /** + * @constructor ObjectNode + * @extends {Node} + * Holds an object with keys/values + * @param {Object.} [properties] object with key/value pairs + */ + constructor (properties) { + super() + this.properties = properties || {} + + // validate input + if (properties) { + if (!(typeof properties === 'object') || + !Object.keys(properties).every(function (key) { + return isNode(properties[key]) + })) { + throw new TypeError('Object containing Nodes expected') + } + } } - this.properties = properties || {} - - // validate input - if (properties) { - if (!(typeof properties === 'object') || !Object.keys(properties).every(function (key) { - return isNode(properties[key]) - })) { - throw new TypeError('Object containing Nodes expected') + static name = name + get type () { return name } + get isObjectNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + const evalEntries = {} + + for (const key in this.properties) { + if (hasOwnProperty(this.properties, key)) { + // we stringify/parse the key here to resolve unicode characters, + // so you cannot create a key like {"co\\u006Estructor": null} + const stringifiedKey = stringify(key) + const parsedKey = JSON.parse(stringifiedKey) + if (!isSafeProperty(this.properties, parsedKey)) { + throw new Error('No access to property "' + parsedKey + '"') + } + + evalEntries[parsedKey] = this.properties[key]._compile(math, argNames) + } } - } - } - ObjectNode.prototype = new Node() - - ObjectNode.prototype.type = 'ObjectNode' - - ObjectNode.prototype.isObjectNode = true - - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - ObjectNode.prototype._compile = function (math, argNames) { - const evalEntries = {} - - for (const key in this.properties) { - if (hasOwnProperty(this.properties, key)) { - // we stringify/parse the key here to resolve unicode characters, - // so you cannot create a key like {"co\\u006Estructor": null} - const stringifiedKey = stringify(key) - const parsedKey = JSON.parse(stringifiedKey) - if (!isSafeProperty(this.properties, parsedKey)) { - throw new Error('No access to property "' + parsedKey + '"') + return function evalObjectNode (scope, args, context) { + const obj = {} + + for (const key in evalEntries) { + if (hasOwnProperty(evalEntries, key)) { + obj[key] = evalEntries[key](scope, args, context) + } } - evalEntries[parsedKey] = this.properties[key]._compile(math, argNames) + return obj } } - return function evalObjectNode (scope, args, context) { - const obj = {} - - for (const key in evalEntries) { - if (hasOwnProperty(evalEntries, key)) { - obj[key] = evalEntries[key](scope, args, context) + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + for (const key in this.properties) { + if (hasOwnProperty(this.properties, key)) { + callback( + this.properties[key], 'properties[' + stringify(key) + ']', this) } } - - return obj } - } - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - ObjectNode.prototype.forEach = function (callback) { - for (const key in this.properties) { - if (hasOwnProperty(this.properties, key)) { - callback(this.properties[key], 'properties[' + stringify(key) + ']', this) + /** + * Create a new ObjectNode whose children are the results of calling + * the provided callback function for each child of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {ObjectNode} Returns a transformed copy of the node + */ + map (callback) { + const properties = {} + for (const key in this.properties) { + if (hasOwnProperty(this.properties, key)) { + properties[key] = this._ifNode( + callback( + this.properties[key], 'properties[' + stringify(key) + ']', this)) + } } + return new ObjectNode(properties) } - } - /** - * Create a new ObjectNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {ObjectNode} Returns a transformed copy of the node - */ - ObjectNode.prototype.map = function (callback) { - const properties = {} - for (const key in this.properties) { - if (hasOwnProperty(this.properties, key)) { - properties[key] = this._ifNode(callback(this.properties[key], - 'properties[' + stringify(key) + ']', this)) + /** + * Create a clone of this node, a shallow copy + * @return {ObjectNode} + */ + clone () { + const properties = {} + for (const key in this.properties) { + if (hasOwnProperty(this.properties, key)) { + properties[key] = this.properties[key] + } } + return new ObjectNode(properties) } - return new ObjectNode(properties) - } - /** - * Create a clone of this node, a shallow copy - * @return {ObjectNode} - */ - ObjectNode.prototype.clone = function () { - const properties = {} - for (const key in this.properties) { - if (hasOwnProperty(this.properties, key)) { - properties[key] = this.properties[key] + /** + * Get string representation + * @param {Object} options + * @return {string} str + * @override + */ + _toString (options) { + const entries = [] + for (const key in this.properties) { + if (hasOwnProperty(this.properties, key)) { + entries.push( + stringify(key) + ': ' + this.properties[key].toString(options)) + } } + return '{' + entries.join(', ') + '}' } - return new ObjectNode(properties) - } - /** - * Get string representation - * @param {Object} options - * @return {string} str - * @override - */ - ObjectNode.prototype._toString = function (options) { - const entries = [] - for (const key in this.properties) { - if (hasOwnProperty(this.properties, key)) { - entries.push(stringify(key) + ': ' + this.properties[key].toString(options)) + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { + mathjs: name, + properties: this.properties } } - return '{' + entries.join(', ') + '}' - } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - ObjectNode.prototype.toJSON = function () { - return { - mathjs: 'ObjectNode', - properties: this.properties + /** + * Instantiate an OperatorNode from its JSON representation + * @param {Object} json An object structured like + * `{"mathjs": "ObjectNode", "properties": {...}}`, + * where mathjs is optional + * @returns {ObjectNode} + */ + static fromJSON (json) { + return new ObjectNode(json.properties) } - } - /** - * Instantiate an OperatorNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "ObjectNode", "properties": {...}}`, - * where mathjs is optional - * @returns {ObjectNode} - */ - ObjectNode.fromJSON = function (json) { - return new ObjectNode(json.properties) - } - - /** - * Get HTML representation - * @param {Object} options - * @return {string} str - * @override - */ - ObjectNode.prototype.toHTML = function (options) { - const entries = [] - for (const key in this.properties) { - if (hasOwnProperty(this.properties, key)) { - entries.push('' + escape(key) + '' + ':' + this.properties[key].toHTML(options)) + /** + * Get HTML representation + * @param {Object} options + * @return {string} str + * @override + */ + toHTML (options) { + const entries = [] + for (const key in this.properties) { + if (hasOwnProperty(this.properties, key)) { + entries.push( + '' + escape(key) + '' + + '' + + ':' + this.properties[key].toHTML(options)) + } } + return '{' + + entries.join(',') + + '}' } - return '{' + entries.join(',') + '}' - } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} str - */ - ObjectNode.prototype._toTex = function (options) { - const entries = [] - for (const key in this.properties) { - if (hasOwnProperty(this.properties, key)) { - entries.push('\\mathbf{' + key + ':} & ' + this.properties[key].toTex(options) + '\\\\') + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} str + */ + _toTex (options) { + const entries = [] + for (const key in this.properties) { + if (hasOwnProperty(this.properties, key)) { + entries.push( + '\\mathbf{' + key + ':} & ' + + this.properties[key].toTex(options) + '\\\\') + } } + const tex = '\\left\\{\\begin{array}{ll}' + entries.join('\n') + + '\\end{array}\\right\\}' + return tex } - return `\\left\\{\\begin{array}{ll}${entries.join('\n')}\\end{array}\\right\\}` } return ObjectNode diff --git a/src/expression/node/OperatorNode.js b/src/expression/node/OperatorNode.js index 5dc5c75df4..21eeeb744c 100644 --- a/src/expression/node/OperatorNode.js +++ b/src/expression/node/OperatorNode.js @@ -1,4 +1,4 @@ -import { isNode } from '../../utils/is.js' +import { isNode, isConstantNode, isOperatorNode, isParenthesisNode } from '../../utils/is.js' import { map } from '../../utils/array.js' import { escape } from '../../utils/string.js' import { getSafeProperty, isSafeMethod } from '../../utils/customs.js' @@ -13,141 +13,22 @@ const dependencies = [ export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { /** - * @constructor OperatorNode - * @extends {Node} - * An operator with two arguments, like 2+3 - * - * @param {string} op Operator name, for example '+' - * @param {string} fn Function name, for example 'add' - * @param {Node[]} args Operator arguments - * @param {boolean} [implicit] Is this an implicit multiplication? - * @param {boolean} [isPercentage] Is this an percentage Operation? - */ - function OperatorNode (op, fn, args, implicit, isPercentage) { - if (!(this instanceof OperatorNode)) { - throw new SyntaxError('Constructor must be called with the new operator') - } - - // validate input - if (typeof op !== 'string') { - throw new TypeError('string expected for parameter "op"') - } - if (typeof fn !== 'string') { - throw new TypeError('string expected for parameter "fn"') - } - if (!Array.isArray(args) || !args.every(isNode)) { - throw new TypeError('Array containing Nodes expected for parameter "args"') - } - - this.implicit = (implicit === true) - this.isPercentage = (isPercentage === true) - this.op = op - this.fn = fn - this.args = args || [] - } - - OperatorNode.prototype = new Node() - - OperatorNode.prototype.type = 'OperatorNode' - - OperatorNode.prototype.isOperatorNode = true - - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - OperatorNode.prototype._compile = function (math, argNames) { - // validate fn - if (typeof this.fn !== 'string' || !isSafeMethod(math, this.fn)) { - if (!math[this.fn]) { - throw new Error('Function ' + this.fn + ' missing in provided namespace "math"') - } else { - throw new Error('No access to function "' + this.fn + '"') - } - } - - const fn = getSafeProperty(math, this.fn) - const evalArgs = map(this.args, function (arg) { - return arg._compile(math, argNames) - }) - - if (evalArgs.length === 1) { - const evalArg0 = evalArgs[0] - return function evalOperatorNode (scope, args, context) { - return fn(evalArg0(scope, args, context)) - } - } else if (evalArgs.length === 2) { - const evalArg0 = evalArgs[0] - const evalArg1 = evalArgs[1] - return function evalOperatorNode (scope, args, context) { - return fn(evalArg0(scope, args, context), evalArg1(scope, args, context)) - } - } else { - return function evalOperatorNode (scope, args, context) { - return fn.apply(null, map(evalArgs, function (evalArg) { - return evalArg(scope, args, context) - })) - } - } - } - - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback + * Returns true if the expression starts with a constant, under + * the current parenthesization: + * @param {Node} expression + * @param {string} parenthesis + * @return {boolean} */ - OperatorNode.prototype.forEach = function (callback) { - for (let i = 0; i < this.args.length; i++) { - callback(this.args[i], 'args[' + i + ']', this) + function startsWithConstant (expr, parenthesis) { + let curNode = expr + if (parenthesis === 'auto') { + while (isParenthesisNode(curNode)) curNode = curNode.content } - } - - /** - * Create a new OperatorNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {OperatorNode} Returns a transformed copy of the node - */ - OperatorNode.prototype.map = function (callback) { - const args = [] - for (let i = 0; i < this.args.length; i++) { - args[i] = this._ifNode(callback(this.args[i], 'args[' + i + ']', this)) + if (isConstantNode(curNode)) return true + if (isOperatorNode(curNode)) { + return startsWithConstant(curNode.args[0], parenthesis) } - return new OperatorNode(this.op, this.fn, args, this.implicit, this.isPercentage) - } - - /** - * Create a clone of this node, a shallow copy - * @return {OperatorNode} - */ - OperatorNode.prototype.clone = function () { - return new OperatorNode(this.op, this.fn, this.args.slice(0), this.implicit, this.isPercentage) - } - - /** - * Check whether this is an unary OperatorNode: - * has exactly one argument, like `-a`. - * @return {boolean} Returns true when an unary operator node, false otherwise. - */ - OperatorNode.prototype.isUnary = function () { - return this.args.length === 1 - } - - /** - * Check whether this is a binary OperatorNode: - * has exactly two arguments, like `a + b`. - * @return {boolean} Returns true when a binary operator node, false otherwise. - */ - OperatorNode.prototype.isBinary = function () { - return this.args.length === 2 + return false } /** @@ -165,7 +46,7 @@ export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({ */ function calculateNecessaryParentheses (root, parenthesis, implicit, args, latex) { // precedence of the root OperatorNode - const precedence = getPrecedence(root, parenthesis) + const precedence = getPrecedence(root, parenthesis, implicit) const associativity = getAssociativity(root, parenthesis) if ((parenthesis === 'all') || ((args.length > 2) && (root.getIdentifier() !== 'OperatorNode:add') && (root.getIdentifier() !== 'OperatorNode:multiply'))) { @@ -191,7 +72,7 @@ export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({ case 1: // unary operators { // precedence of the operand - const operandPrecedence = getPrecedence(args[0], parenthesis) + const operandPrecedence = getPrecedence(args[0], parenthesis, implicit, root) // handle special cases for LaTeX, where some of the parentheses aren't needed if (latex && (operandPrecedence !== null)) { @@ -236,7 +117,7 @@ export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({ { let lhsParens // left hand side needs parenthesis? // precedence of the left hand side - const lhsPrecedence = getPrecedence(args[0], parenthesis) + const lhsPrecedence = getPrecedence(args[0], parenthesis, implicit, root) // is the root node associative with the left hand side const assocWithLhs = isAssociativeWith(root, args[0], parenthesis) @@ -258,7 +139,7 @@ export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({ let rhsParens // right hand side needs parenthesis? // precedence of the right hand side - const rhsPrecedence = getPrecedence(args[1], parenthesis) + const rhsPrecedence = getPrecedence(args[1], parenthesis, implicit, root) // is the root node associative with the right hand side? const assocWithRhs = isAssociativeWith(root, args[1], parenthesis) @@ -322,7 +203,7 @@ export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({ default: if ((root.getIdentifier() === 'OperatorNode:add') || (root.getIdentifier() === 'OperatorNode:multiply')) { result = args.map(function (arg) { - const argPrecedence = getPrecedence(arg, parenthesis) + const argPrecedence = getPrecedence(arg, parenthesis, implicit, root) const assocWithArg = isAssociativeWith(root, arg, parenthesis) const argAssociativity = getAssociativity(arg, parenthesis) if (argPrecedence === null) { @@ -340,277 +221,476 @@ export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({ break } - // handles an edge case of 'auto' parentheses with implicit multiplication of ConstantNode - // In that case print parentheses for ParenthesisNodes even though they normally wouldn't be - // printed. - if ((args.length >= 2) && (root.getIdentifier() === 'OperatorNode:multiply') && root.implicit && (parenthesis === 'auto') && (implicit === 'hide')) { - result = args.map(function (arg, index) { - const isParenthesisNode = (arg.getIdentifier() === 'ParenthesisNode') - if (result[index] || isParenthesisNode) { // put in parenthesis? - return true + // Handles an edge case of parentheses with implicit multiplication + // of ConstantNode. + // In that case, parenthesize ConstantNodes that follow an unparenthesized + // expression, even though they normally wouldn't be printed. + if (args.length >= 2 && root.getIdentifier() === 'OperatorNode:multiply' && + root.implicit && parenthesis !== 'all' && implicit === 'hide') { + for (let i = 1; i < result.length; ++i) { + if (startsWithConstant(args[i], parenthesis) && !result[i - 1] && + (parenthesis !== 'keep' || !isParenthesisNode(args[i - 1]))) { + result[i] = true } - - return false - }) + } } return result } - /** - * Get string representation. - * @param {Object} options - * @return {string} str - */ - OperatorNode.prototype._toString = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - const implicit = (options && options.implicit) ? options.implicit : 'hide' - const args = this.args - const parens = calculateNecessaryParentheses(this, parenthesis, implicit, args, false) - - if (args.length === 1) { // unary operators - const assoc = getAssociativity(this, parenthesis) - - let operand = args[0].toString(options) - if (parens[0]) { - operand = '(' + operand + ')' + class OperatorNode extends Node { + /** + * @constructor OperatorNode + * @extends {Node} + * An operator with two arguments, like 2+3 + * + * @param {string} op Operator name, for example '+' + * @param {string} fn Function name, for example 'add' + * @param {Node[]} args Operator arguments + * @param {boolean} [implicit] Is this an implicit multiplication? + * @param {boolean} [isPercentage] Is this an percentage Operation? + */ + constructor (op, fn, args, implicit, isPercentage) { + super() + // validate input + if (typeof op !== 'string') { + throw new TypeError('string expected for parameter "op"') } - - // for example for "not", we want a space between operand and argument - const opIsNamed = /[a-zA-Z]+/.test(this.op) - - if (assoc === 'right') { // prefix operator - return this.op + (opIsNamed ? ' ' : '') + operand - } else if (assoc === 'left') { // postfix - return operand + (opIsNamed ? ' ' : '') + this.op + if (typeof fn !== 'string') { + throw new TypeError('string expected for parameter "fn"') } - - // fall back to postfix - return operand + this.op - } else if (args.length === 2) { - let lhs = args[0].toString(options) // left hand side - let rhs = args[1].toString(options) // right hand side - if (parens[0]) { // left hand side in parenthesis? - lhs = '(' + lhs + ')' - } - if (parens[1]) { // right hand side in parenthesis? - rhs = '(' + rhs + ')' + if (!Array.isArray(args) || !args.every(isNode)) { + throw new TypeError( + 'Array containing Nodes expected for parameter "args"') } - if (this.implicit && (this.getIdentifier() === 'OperatorNode:multiply') && (implicit === 'hide')) { - return lhs + ' ' + rhs - } + this.implicit = (implicit === true) + this.isPercentage = (isPercentage === true) + this.op = op + this.fn = fn + this.args = args || [] + } - return lhs + ' ' + this.op + ' ' + rhs - } else if ((args.length > 2) && ((this.getIdentifier() === 'OperatorNode:add') || (this.getIdentifier() === 'OperatorNode:multiply'))) { - const stringifiedArgs = args.map(function (arg, index) { - arg = arg.toString(options) - if (parens[index]) { // put in parenthesis? - arg = '(' + arg + ')' + static name = name + get type () { return name } + get isOperatorNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + // validate fn + if (typeof this.fn !== 'string' || !isSafeMethod(math, this.fn)) { + if (!math[this.fn]) { + throw new Error( + 'Function ' + this.fn + ' missing in provided namespace "math"') + } else { + throw new Error('No access to function "' + this.fn + '"') } + } - return arg + const fn = getSafeProperty(math, this.fn) + const evalArgs = map(this.args, function (arg) { + return arg._compile(math, argNames) }) - if (this.implicit && (this.getIdentifier() === 'OperatorNode:multiply') && (implicit === 'hide')) { - return stringifiedArgs.join(' ') + if (evalArgs.length === 1) { + const evalArg0 = evalArgs[0] + return function evalOperatorNode (scope, args, context) { + return fn(evalArg0(scope, args, context)) + } + } else if (evalArgs.length === 2) { + const evalArg0 = evalArgs[0] + const evalArg1 = evalArgs[1] + return function evalOperatorNode (scope, args, context) { + return fn( + evalArg0(scope, args, context), + evalArg1(scope, args, context)) + } + } else { + return function evalOperatorNode (scope, args, context) { + return fn.apply(null, map(evalArgs, function (evalArg) { + return evalArg(scope, args, context) + })) + } } + } - return stringifiedArgs.join(' ' + this.op + ' ') - } else { - // fallback to formatting as a function call - return this.fn + '(' + this.args.join(', ') + ')' + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + for (let i = 0; i < this.args.length; i++) { + callback(this.args[i], 'args[' + i + ']', this) + } } - } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - OperatorNode.prototype.toJSON = function () { - return { - mathjs: 'OperatorNode', - op: this.op, - fn: this.fn, - args: this.args, - implicit: this.implicit, - isPercentage: this.isPercentage + /** + * Create a new OperatorNode whose children are the results of calling + * the provided callback function for each child of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {OperatorNode} Returns a transformed copy of the node + */ + map (callback) { + const args = [] + for (let i = 0; i < this.args.length; i++) { + args[i] = this._ifNode(callback(this.args[i], 'args[' + i + ']', this)) + } + return new OperatorNode( + this.op, this.fn, args, this.implicit, this.isPercentage) } - } - /** - * Instantiate an OperatorNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "OperatorNode", "op": "+", "fn": "add", "args": [...], "implicit": false, "isPercentage":false}`, - * where mathjs is optional - * @returns {OperatorNode} - */ - OperatorNode.fromJSON = function (json) { - return new OperatorNode(json.op, json.fn, json.args, json.implicit, json.isPercentage) - } + /** + * Create a clone of this node, a shallow copy + * @return {OperatorNode} + */ + clone () { + return new OperatorNode( + this.op, this.fn, this.args.slice(0), this.implicit, this.isPercentage) + } - /** - * Get HTML representation. - * @param {Object} options - * @return {string} str - */ - OperatorNode.prototype.toHTML = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - const implicit = (options && options.implicit) ? options.implicit : 'hide' - const args = this.args - const parens = calculateNecessaryParentheses(this, parenthesis, implicit, args, false) - - if (args.length === 1) { // unary operators - const assoc = getAssociativity(this, parenthesis) - - let operand = args[0].toHTML(options) - if (parens[0]) { - operand = '(' + operand + ')' - } + /** + * Check whether this is an unary OperatorNode: + * has exactly one argument, like `-a`. + * @return {boolean} + * Returns true when an unary operator node, false otherwise. + */ + isUnary () { + return this.args.length === 1 + } - if (assoc === 'right') { // prefix operator - return '' + escape(this.op) + '' + operand - } else { // postfix when assoc === 'left' or undefined - return operand + '' + escape(this.op) + '' - } - } else if (args.length === 2) { // binary operatoes - let lhs = args[0].toHTML(options) // left hand side - let rhs = args[1].toHTML(options) // right hand side - if (parens[0]) { // left hand side in parenthesis? - lhs = '(' + lhs + ')' - } - if (parens[1]) { // right hand side in parenthesis? - rhs = '(' + rhs + ')' - } + /** + * Check whether this is a binary OperatorNode: + * has exactly two arguments, like `a + b`. + * @return {boolean} + * Returns true when a binary operator node, false otherwise. + */ + isBinary () { + return this.args.length === 2 + } - if (this.implicit && (this.getIdentifier() === 'OperatorNode:multiply') && (implicit === 'hide')) { - return lhs + '' + rhs - } + /** + * Get string representation. + * @param {Object} options + * @return {string} str + */ + _toString (options) { + const parenthesis = + (options && options.parenthesis) ? options.parenthesis : 'keep' + const implicit = (options && options.implicit) ? options.implicit : 'hide' + const args = this.args + const parens = + calculateNecessaryParentheses(this, parenthesis, implicit, args, false) + + if (args.length === 1) { // unary operators + const assoc = getAssociativity(this, parenthesis) + + let operand = args[0].toString(options) + if (parens[0]) { + operand = '(' + operand + ')' + } + + // for example for "not", we want a space between operand and argument + const opIsNamed = /[a-zA-Z]+/.test(this.op) - return lhs + '' + escape(this.op) + '' + rhs - } else { - const stringifiedArgs = args.map(function (arg, index) { - arg = arg.toHTML(options) - if (parens[index]) { // put in parenthesis? - arg = '(' + arg + ')' + if (assoc === 'right') { // prefix operator + return this.op + (opIsNamed ? ' ' : '') + operand + } else if (assoc === 'left') { // postfix + return operand + (opIsNamed ? ' ' : '') + this.op } - return arg - }) + // fall back to postfix + return operand + this.op + } else if (args.length === 2) { + let lhs = args[0].toString(options) // left hand side + let rhs = args[1].toString(options) // right hand side + if (parens[0]) { // left hand side in parenthesis? + lhs = '(' + lhs + ')' + } + if (parens[1]) { // right hand side in parenthesis? + rhs = '(' + rhs + ')' + } - if ((args.length > 2) && ((this.getIdentifier() === 'OperatorNode:add') || (this.getIdentifier() === 'OperatorNode:multiply'))) { - if (this.implicit && (this.getIdentifier() === 'OperatorNode:multiply') && (implicit === 'hide')) { - return stringifiedArgs.join('') + if (this.implicit && + (this.getIdentifier() === 'OperatorNode:multiply') && + (implicit === 'hide')) { + return lhs + ' ' + rhs } - return stringifiedArgs.join('' + escape(this.op) + '') + return lhs + ' ' + this.op + ' ' + rhs + } else if ((args.length > 2) && + ((this.getIdentifier() === 'OperatorNode:add') || + (this.getIdentifier() === 'OperatorNode:multiply'))) { + const stringifiedArgs = args.map(function (arg, index) { + arg = arg.toString(options) + if (parens[index]) { // put in parenthesis? + arg = '(' + arg + ')' + } + + return arg + }) + + if (this.implicit && + (this.getIdentifier() === 'OperatorNode:multiply') && + (implicit === 'hide')) { + return stringifiedArgs.join(' ') + } + + return stringifiedArgs.join(' ' + this.op + ' ') } else { // fallback to formatting as a function call - return '' + escape(this.fn) + '(' + stringifiedArgs.join(',') + ')' + return this.fn + '(' + this.args.join(', ') + ')' } } - } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} str - */ - OperatorNode.prototype._toTex = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - const implicit = (options && options.implicit) ? options.implicit : 'hide' - const args = this.args - const parens = calculateNecessaryParentheses(this, parenthesis, implicit, args, true) - let op = latexOperators[this.fn] - op = typeof op === 'undefined' ? this.op : op // fall back to using this.op - - if (args.length === 1) { // unary operators - const assoc = getAssociativity(this, parenthesis) - - let operand = args[0].toTex(options) - if (parens[0]) { - operand = `\\left(${operand}\\right)` + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { + mathjs: name, + op: this.op, + fn: this.fn, + args: this.args, + implicit: this.implicit, + isPercentage: this.isPercentage } + } - if (assoc === 'right') { // prefix operator - return op + operand - } else if (assoc === 'left') { // postfix operator - return operand + op - } + /** + * Instantiate an OperatorNode from its JSON representation + * @param {Object} json + * An object structured like + * ``` + * {"mathjs": "OperatorNode", + * "op": "+", "fn": "add", "args": [...], + * "implicit": false, + * "isPercentage":false} + * ``` + * where mathjs is optional + * @returns {OperatorNode} + */ + static fromJSON (json) { + return new OperatorNode( + json.op, json.fn, json.args, json.implicit, json.isPercentage) + } - // fall back to postfix - return operand + op - } else if (args.length === 2) { // binary operators - const lhs = args[0] // left hand side - let lhsTex = lhs.toTex(options) - if (parens[0]) { - lhsTex = `\\left(${lhsTex}\\right)` - } + /** + * Get HTML representation. + * @param {Object} options + * @return {string} str + */ + toHTML (options) { + const parenthesis = + (options && options.parenthesis) ? options.parenthesis : 'keep' + const implicit = (options && options.implicit) ? options.implicit : 'hide' + const args = this.args + const parens = + calculateNecessaryParentheses(this, parenthesis, implicit, args, false) + + if (args.length === 1) { // unary operators + const assoc = getAssociativity(this, parenthesis) + + let operand = args[0].toHTML(options) + if (parens[0]) { + operand = + '(' + + operand + + ')' + } - const rhs = args[1] // right hand side - let rhsTex = rhs.toTex(options) - if (parens[1]) { - rhsTex = `\\left(${rhsTex}\\right)` - } + if (assoc === 'right') { // prefix operator + return '' + escape(this.op) + '' + + operand + } else { // postfix when assoc === 'left' or undefined + return operand + + '' + escape(this.op) + '' + } + } else if (args.length === 2) { // binary operatoes + let lhs = args[0].toHTML(options) // left hand side + let rhs = args[1].toHTML(options) // right hand side + if (parens[0]) { // left hand side in parenthesis? + lhs = '(' + + lhs + + ')' + } + if (parens[1]) { // right hand side in parenthesis? + rhs = '(' + + rhs + + ')' + } - // handle some exceptions (due to the way LaTeX works) - let lhsIdentifier - if (parenthesis === 'keep') { - lhsIdentifier = lhs.getIdentifier() + if (this.implicit && + (this.getIdentifier() === 'OperatorNode:multiply') && + (implicit === 'hide')) { + return lhs + + '' + rhs + } + + return lhs + + '' + escape(this.op) + '' + + rhs } else { - // Ignore ParenthesisNodes if in 'keep' mode - lhsIdentifier = lhs.getContent().getIdentifier() - } - switch (this.getIdentifier()) { - case 'OperatorNode:divide': - // op contains '\\frac' at this point - return op + '{' + lhsTex + '}' + '{' + rhsTex + '}' - case 'OperatorNode:pow': - lhsTex = '{' + lhsTex + '}' - rhsTex = '{' + rhsTex + '}' - switch (lhsIdentifier) { - case 'ConditionalNode': // - case 'OperatorNode:divide': - lhsTex = `\\left(${lhsTex}\\right)` + const stringifiedArgs = args.map(function (arg, index) { + arg = arg.toHTML(options) + if (parens[index]) { // put in parenthesis? + arg = + '(' + + arg + + ')' } - break - case 'OperatorNode:multiply': - if (this.implicit && (implicit === 'hide')) { - return lhsTex + '~' + rhsTex + + return arg + }) + + if ((args.length > 2) && + ((this.getIdentifier() === 'OperatorNode:add') || + (this.getIdentifier() === 'OperatorNode:multiply'))) { + if (this.implicit && + (this.getIdentifier() === 'OperatorNode:multiply') && + (implicit === 'hide')) { + return stringifiedArgs.join( + '') } + + return stringifiedArgs.join( + '' + escape(this.op) + '') + } else { + // fallback to formatting as a function call + return '' + escape(this.fn) + + '' + + '(' + + stringifiedArgs.join(',') + + ')' + } } - return lhsTex + op + rhsTex - } else if ((args.length > 2) && ((this.getIdentifier() === 'OperatorNode:add') || (this.getIdentifier() === 'OperatorNode:multiply'))) { - const texifiedArgs = args.map(function (arg, index) { - arg = arg.toTex(options) - if (parens[index]) { - arg = `\\left(${arg}\\right)` + } + + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} str + */ + _toTex (options) { + const parenthesis = + (options && options.parenthesis) ? options.parenthesis : 'keep' + const implicit = (options && options.implicit) ? options.implicit : 'hide' + const args = this.args + const parens = + calculateNecessaryParentheses(this, parenthesis, implicit, args, true) + + let op = latexOperators[this.fn] + op = typeof op === 'undefined' ? this.op : op // fall back to using this.op + + if (args.length === 1) { // unary operators + const assoc = getAssociativity(this, parenthesis) + + let operand = args[0].toTex(options) + if (parens[0]) { + operand = `\\left(${operand}\\right)` } - return arg - }) - if ((this.getIdentifier() === 'OperatorNode:multiply') && this.implicit) { - return texifiedArgs.join('~') - } + if (assoc === 'right') { // prefix operator + return op + operand + } else if (assoc === 'left') { // postfix operator + return operand + op + } - return texifiedArgs.join(op) - } else { - // fall back to formatting as a function call - // as this is a fallback, it doesn't use - // fancy function names - return '\\mathrm{' + this.fn + '}\\left(' + + // fall back to postfix + return operand + op + } else if (args.length === 2) { // binary operators + const lhs = args[0] // left hand side + let lhsTex = lhs.toTex(options) + if (parens[0]) { + lhsTex = `\\left(${lhsTex}\\right)` + } + + const rhs = args[1] // right hand side + let rhsTex = rhs.toTex(options) + if (parens[1]) { + rhsTex = `\\left(${rhsTex}\\right)` + } + + // handle some exceptions (due to the way LaTeX works) + let lhsIdentifier + if (parenthesis === 'keep') { + lhsIdentifier = lhs.getIdentifier() + } else { + // Ignore ParenthesisNodes if in 'keep' mode + lhsIdentifier = lhs.getContent().getIdentifier() + } + switch (this.getIdentifier()) { + case 'OperatorNode:divide': + // op contains '\\frac' at this point + return op + '{' + lhsTex + '}' + '{' + rhsTex + '}' + case 'OperatorNode:pow': + lhsTex = '{' + lhsTex + '}' + rhsTex = '{' + rhsTex + '}' + switch (lhsIdentifier) { + case 'ConditionalNode': // + case 'OperatorNode:divide': + lhsTex = `\\left(${lhsTex}\\right)` + } + break + case 'OperatorNode:multiply': + if (this.implicit && (implicit === 'hide')) { + return lhsTex + '~' + rhsTex + } + } + return lhsTex + op + rhsTex + } else if ((args.length > 2) && + ((this.getIdentifier() === 'OperatorNode:add') || + (this.getIdentifier() === 'OperatorNode:multiply'))) { + const texifiedArgs = args.map(function (arg, index) { + arg = arg.toTex(options) + if (parens[index]) { + arg = `\\left(${arg}\\right)` + } + return arg + }) + + if ((this.getIdentifier() === 'OperatorNode:multiply') && + this.implicit && implicit === 'hide') { + return texifiedArgs.join('~') + } + + return texifiedArgs.join(op) + } else { + // fall back to formatting as a function call + // as this is a fallback, it doesn't use + // fancy function names + return '\\mathrm{' + this.fn + '}\\left(' + args.map(function (arg) { return arg.toTex(options) }).join(',') + '\\right)' + } } - } - /** - * Get identifier. - * @return {string} - */ - OperatorNode.prototype.getIdentifier = function () { - return this.type + ':' + this.fn + /** + * Get identifier. + * @return {string} + */ + getIdentifier () { + return this.type + ':' + this.fn + } } return OperatorNode diff --git a/src/expression/node/ParenthesisNode.js b/src/expression/node/ParenthesisNode.js index c59d4243cd..3a6848d3d4 100644 --- a/src/expression/node/ParenthesisNode.js +++ b/src/expression/node/ParenthesisNode.js @@ -7,144 +7,146 @@ const dependencies = [ ] export const createParenthesisNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { - /** - * @constructor ParenthesisNode - * @extends {Node} - * A parenthesis node describes manual parenthesis from the user input - * @param {Node} content - * @extends {Node} - */ - function ParenthesisNode (content) { - if (!(this instanceof ParenthesisNode)) { - throw new SyntaxError('Constructor must be called with the new operator') + class ParenthesisNode extends Node { + /** + * @constructor ParenthesisNode + * @extends {Node} + * A parenthesis node describes manual parenthesis from the user input + * @param {Node} content + * @extends {Node} + */ + constructor (content) { + super() + // validate input + if (!isNode(content)) { + throw new TypeError('Node expected for parameter "content"') + } + + this.content = content } - // validate input - if (!isNode(content)) { - throw new TypeError('Node expected for parameter "content"') + static name = name + get type () { return name } + get isParenthesisNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + return this.content._compile(math, argNames) } - this.content = content - } - - ParenthesisNode.prototype = new Node() - - ParenthesisNode.prototype.type = 'ParenthesisNode' - - ParenthesisNode.prototype.isParenthesisNode = true - - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - ParenthesisNode.prototype._compile = function (math, argNames) { - return this.content._compile(math, argNames) - } - - /** - * Get the content of the current Node. - * @return {Node} content - * @override - **/ - ParenthesisNode.prototype.getContent = function () { - return this.content.getContent() - } + /** + * Get the content of the current Node. + * @return {Node} content + * @override + **/ + getContent () { + return this.content.getContent() + } - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - ParenthesisNode.prototype.forEach = function (callback) { - callback(this.content, 'content', this) - } + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + callback(this.content, 'content', this) + } - /** - * Create a new ParenthesisNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node) : Node} callback - * @returns {ParenthesisNode} Returns a clone of the node - */ - ParenthesisNode.prototype.map = function (callback) { - const content = callback(this.content, 'content', this) - return new ParenthesisNode(content) - } + /** + * Create a new ParenthesisNode whose child is the result of calling + * the provided callback function on the child of this node. + * @param {function(child: Node, path: string, parent: Node) : Node} callback + * @returns {ParenthesisNode} Returns a clone of the node + */ + map (callback) { + const content = callback(this.content, 'content', this) + return new ParenthesisNode(content) + } - /** - * Create a clone of this node, a shallow copy - * @return {ParenthesisNode} - */ - ParenthesisNode.prototype.clone = function () { - return new ParenthesisNode(this.content) - } + /** + * Create a clone of this node, a shallow copy + * @return {ParenthesisNode} + */ + clone () { + return new ParenthesisNode(this.content) + } - /** - * Get string representation - * @param {Object} options - * @return {string} str - * @override - */ - ParenthesisNode.prototype._toString = function (options) { - if ((!options) || (options && !options.parenthesis) || (options && options.parenthesis === 'keep')) { - return '(' + this.content.toString(options) + ')' + /** + * Get string representation + * @param {Object} options + * @return {string} str + * @override + */ + _toString (options) { + if ((!options) || + (options && !options.parenthesis) || + (options && options.parenthesis === 'keep')) { + return '(' + this.content.toString(options) + ')' + } + return this.content.toString(options) } - return this.content.toString(options) - } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - ParenthesisNode.prototype.toJSON = function () { - return { - mathjs: 'ParenthesisNode', - content: this.content + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { mathjs: name, content: this.content } } - } - /** - * Instantiate an ParenthesisNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "ParenthesisNode", "content": ...}`, - * where mathjs is optional - * @returns {ParenthesisNode} - */ - ParenthesisNode.fromJSON = function (json) { - return new ParenthesisNode(json.content) - } + /** + * Instantiate an ParenthesisNode from its JSON representation + * @param {Object} json An object structured like + * `{"mathjs": "ParenthesisNode", "content": ...}`, + * where mathjs is optional + * @returns {ParenthesisNode} + */ + static fromJSON (json) { + return new ParenthesisNode(json.content) + } - /** - * Get HTML representation - * @param {Object} options - * @return {string} str - * @override - */ - ParenthesisNode.prototype.toHTML = function (options) { - if ((!options) || (options && !options.parenthesis) || (options && options.parenthesis === 'keep')) { - return '(' + this.content.toHTML(options) + ')' + /** + * Get HTML representation + * @param {Object} options + * @return {string} str + * @override + */ + toHTML (options) { + if ((!options) || + (options && !options.parenthesis) || + (options && options.parenthesis === 'keep')) { + return '(' + + this.content.toHTML(options) + + ')' + } + return this.content.toHTML(options) } - return this.content.toHTML(options) - } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} str - * @override - */ - ParenthesisNode.prototype._toTex = function (options) { - if ((!options) || (options && !options.parenthesis) || (options && options.parenthesis === 'keep')) { - return `\\left(${this.content.toTex(options)}\\right)` + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} str + * @override + */ + _toTex (options) { + if ((!options) || + (options && !options.parenthesis) || + (options && options.parenthesis === 'keep')) { + return `\\left(${this.content.toTex(options)}\\right)` + } + return this.content.toTex(options) } - return this.content.toTex(options) } return ParenthesisNode diff --git a/src/expression/node/RangeNode.js b/src/expression/node/RangeNode.js index 849f7ba153..dd814d058a 100644 --- a/src/expression/node/RangeNode.js +++ b/src/expression/node/RangeNode.js @@ -8,273 +8,287 @@ const dependencies = [ ] export const createRangeNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { - /** - * @constructor RangeNode - * @extends {Node} - * create a range - * @param {Node} start included lower-bound - * @param {Node} end included upper-bound - * @param {Node} [step] optional step - */ - function RangeNode (start, end, step) { - if (!(this instanceof RangeNode)) { - throw new SyntaxError('Constructor must be called with the new operator') - } - - // validate inputs - if (!isNode(start)) throw new TypeError('Node expected') - if (!isNode(end)) throw new TypeError('Node expected') - if (step && !isNode(step)) throw new TypeError('Node expected') - if (arguments.length > 3) throw new Error('Too many arguments') - - this.start = start // included lower-bound - this.end = end // included upper-bound - this.step = step || null // optional step - } - - RangeNode.prototype = new Node() - - RangeNode.prototype.type = 'RangeNode' - - RangeNode.prototype.isRangeNode = true - - /** - * Check whether the RangeNode needs the `end` symbol to be defined. - * This end is the size of the Matrix in current dimension. - * @return {boolean} - */ - RangeNode.prototype.needsEnd = function () { - // find all `end` symbols in this RangeNode - const endSymbols = this.filter(function (node) { - return isSymbolNode(node) && (node.name === 'end') - }) - - return endSymbols.length > 0 - } - - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - RangeNode.prototype._compile = function (math, argNames) { - const range = math.range - const evalStart = this.start._compile(math, argNames) - const evalEnd = this.end._compile(math, argNames) - - if (this.step) { - const evalStep = this.step._compile(math, argNames) - - return function evalRangeNode (scope, args, context) { - return range( - evalStart(scope, args, context), - evalEnd(scope, args, context), - evalStep(scope, args, context) - ) - } - } else { - return function evalRangeNode (scope, args, context) { - return range( - evalStart(scope, args, context), - evalEnd(scope, args, context) - ) - } - } - } - - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - RangeNode.prototype.forEach = function (callback) { - callback(this.start, 'start', this) - callback(this.end, 'end', this) - if (this.step) { - callback(this.step, 'step', this) - } - } - - /** - * Create a new RangeNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {RangeNode} Returns a transformed copy of the node - */ - RangeNode.prototype.map = function (callback) { - return new RangeNode( - this._ifNode(callback(this.start, 'start', this)), - this._ifNode(callback(this.end, 'end', this)), - this.step && this._ifNode(callback(this.step, 'step', this)) - ) - } - - /** - * Create a clone of this node, a shallow copy - * @return {RangeNode} - */ - RangeNode.prototype.clone = function () { - return new RangeNode(this.start, this.end, this.step && this.step) - } - /** * Calculate the necessary parentheses * @param {Node} node * @param {string} parenthesis + * @param {string} implicit * @return {Object} parentheses * @private */ - function calculateNecessaryParentheses (node, parenthesis) { - const precedence = getPrecedence(node, parenthesis) + function calculateNecessaryParentheses (node, parenthesis, implicit) { + const precedence = getPrecedence(node, parenthesis, implicit) const parens = {} - const startPrecedence = getPrecedence(node.start, parenthesis) + const startPrecedence = getPrecedence(node.start, parenthesis, implicit) parens.start = ((startPrecedence !== null) && (startPrecedence <= precedence)) || (parenthesis === 'all') if (node.step) { - const stepPrecedence = getPrecedence(node.step, parenthesis) + const stepPrecedence = getPrecedence(node.step, parenthesis, implicit) parens.step = ((stepPrecedence !== null) && (stepPrecedence <= precedence)) || (parenthesis === 'all') } - const endPrecedence = getPrecedence(node.end, parenthesis) + const endPrecedence = getPrecedence(node.end, parenthesis, implicit) parens.end = ((endPrecedence !== null) && (endPrecedence <= precedence)) || (parenthesis === 'all') return parens } - /** - * Get string representation - * @param {Object} options - * @return {string} str - */ - RangeNode.prototype._toString = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - const parens = calculateNecessaryParentheses(this, parenthesis) - - // format string as start:step:stop - let str + class RangeNode extends Node { + /** + * @constructor RangeNode + * @extends {Node} + * create a range + * @param {Node} start included lower-bound + * @param {Node} end included upper-bound + * @param {Node} [step] optional step + */ + constructor (start, end, step) { + super() + // validate inputs + if (!isNode(start)) throw new TypeError('Node expected') + if (!isNode(end)) throw new TypeError('Node expected') + if (step && !isNode(step)) throw new TypeError('Node expected') + if (arguments.length > 3) throw new Error('Too many arguments') + + this.start = start // included lower-bound + this.end = end // included upper-bound + this.step = step || null // optional step + } - let start = this.start.toString(options) - if (parens.start) { - start = '(' + start + ')' + static name = name + get type () { return name } + get isRangeNode () { return true } + + /** + * Check whether the RangeNode needs the `end` symbol to be defined. + * This end is the size of the Matrix in current dimension. + * @return {boolean} + */ + needsEnd () { + // find all `end` symbols in this RangeNode + const endSymbols = this.filter(function (node) { + return isSymbolNode(node) && (node.name === 'end') + }) + + return endSymbols.length > 0 } - str = start - if (this.step) { - let step = this.step.toString(options) - if (parens.step) { - step = '(' + step + ')' + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + const range = math.range + const evalStart = this.start._compile(math, argNames) + const evalEnd = this.end._compile(math, argNames) + + if (this.step) { + const evalStep = this.step._compile(math, argNames) + + return function evalRangeNode (scope, args, context) { + return range( + evalStart(scope, args, context), + evalEnd(scope, args, context), + evalStep(scope, args, context) + ) + } + } else { + return function evalRangeNode (scope, args, context) { + return range( + evalStart(scope, args, context), + evalEnd(scope, args, context) + ) + } } - str += ':' + step } - let end = this.end.toString(options) - if (parens.end) { - end = '(' + end + ')' + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + callback(this.start, 'start', this) + callback(this.end, 'end', this) + if (this.step) { + callback(this.step, 'step', this) + } } - str += ':' + end - - return str - } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - RangeNode.prototype.toJSON = function () { - return { - mathjs: 'RangeNode', - start: this.start, - end: this.end, - step: this.step + /** + * Create a new RangeNode whose children are the results of calling + * the provided callback function for each child of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {RangeNode} Returns a transformed copy of the node + */ + map (callback) { + return new RangeNode( + this._ifNode(callback(this.start, 'start', this)), + this._ifNode(callback(this.end, 'end', this)), + this.step && this._ifNode(callback(this.step, 'step', this)) + ) } - } - /** - * Instantiate an RangeNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "RangeNode", "start": ..., "end": ..., "step": ...}`, - * where mathjs is optional - * @returns {RangeNode} - */ - RangeNode.fromJSON = function (json) { - return new RangeNode(json.start, json.end, json.step) - } + /** + * Create a clone of this node, a shallow copy + * @return {RangeNode} + */ + clone () { + return new RangeNode(this.start, this.end, this.step && this.step) + } - /** - * Get HTML representation - * @param {Object} options - * @return {string} str - */ - RangeNode.prototype.toHTML = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - const parens = calculateNecessaryParentheses(this, parenthesis) + /** + * Get string representation + * @param {Object} options + * @return {string} str + */ + _toString (options) { + const parenthesis = + (options && options.parenthesis) ? options.parenthesis : 'keep' + const parens = + calculateNecessaryParentheses( + this, parenthesis, options && options.implicit) + + // format string as start:step:stop + let str + + let start = this.start.toString(options) + if (parens.start) { + start = '(' + start + ')' + } + str = start + + if (this.step) { + let step = this.step.toString(options) + if (parens.step) { + step = '(' + step + ')' + } + str += ':' + step + } - // format string as start:step:stop - let str + let end = this.end.toString(options) + if (parens.end) { + end = '(' + end + ')' + } + str += ':' + end - let start = this.start.toHTML(options) - if (parens.start) { - start = '(' + start + ')' + return str } - str = start - if (this.step) { - let step = this.step.toHTML(options) - if (parens.step) { - step = '(' + step + ')' + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { + mathjs: name, + start: this.start, + end: this.end, + step: this.step } - str += ':' + step } - let end = this.end.toHTML(options) - if (parens.end) { - end = '(' + end + ')' + /** + * Instantiate an RangeNode from its JSON representation + * @param {Object} json + * An object structured like + * `{"mathjs": "RangeNode", "start": ..., "end": ..., "step": ...}`, + * where mathjs is optional + * @returns {RangeNode} + */ + static fromJSON (json) { + return new RangeNode(json.start, json.end, json.step) } - str += ':' + end - return str - } + /** + * Get HTML representation + * @param {Object} options + * @return {string} str + */ + toHTML (options) { + const parenthesis = + (options && options.parenthesis) ? options.parenthesis : 'keep' + const parens = + calculateNecessaryParentheses( + this, parenthesis, options && options.implicit) + + // format string as start:step:stop + let str + + let start = this.start.toHTML(options) + if (parens.start) { + start = '(' + + start + + ')' + } + str = start + + if (this.step) { + let step = this.step.toHTML(options) + if (parens.step) { + step = '(' + + step + + ')' + } + str += ':' + step + } - /** - * Get LaTeX representation - * @params {Object} options - * @return {string} str - */ - RangeNode.prototype._toTex = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - const parens = calculateNecessaryParentheses(this, parenthesis) + let end = this.end.toHTML(options) + if (parens.end) { + end = '(' + + end + + ')' + } + str += ':' + end - let str = this.start.toTex(options) - if (parens.start) { - str = `\\left(${str}\\right)` + return str } - if (this.step) { - let step = this.step.toTex(options) - if (parens.step) { - step = `\\left(${step}\\right)` + /** + * Get LaTeX representation + * @params {Object} options + * @return {string} str + */ + _toTex (options) { + const parenthesis = + (options && options.parenthesis) ? options.parenthesis : 'keep' + const parens = + calculateNecessaryParentheses( + this, parenthesis, options && options.implicit) + + let str = this.start.toTex(options) + if (parens.start) { + str = `\\left(${str}\\right)` } - str += ':' + step - } - let end = this.end.toTex(options) - if (parens.end) { - end = `\\left(${end}\\right)` - } - str += ':' + end + if (this.step) { + let step = this.step.toTex(options) + if (parens.step) { + step = `\\left(${step}\\right)` + } + str += ':' + step + } + + let end = this.end.toTex(options) + if (parens.end) { + end = `\\left(${end}\\right)` + } + str += ':' + end - return str + return str + } } return RangeNode diff --git a/src/expression/node/RelationalNode.js b/src/expression/node/RelationalNode.js index 80af63bb3c..66dffea1c0 100644 --- a/src/expression/node/RelationalNode.js +++ b/src/expression/node/RelationalNode.js @@ -10,204 +10,221 @@ const dependencies = [ ] export const createRelationalNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { - /** - * A node representing a chained conditional expression, such as 'x > y > z' - * - * @param {String[]} conditionals An array of conditional operators used to compare the parameters - * @param {Node[]} params The parameters that will be compared - * - * @constructor RelationalNode - * @extends {Node} - */ - function RelationalNode (conditionals, params) { - if (!(this instanceof RelationalNode)) { - throw new SyntaxError('Constructor must be called with the new operator') - } - if (!Array.isArray(conditionals)) throw new TypeError('Parameter conditionals must be an array') - if (!Array.isArray(params)) throw new TypeError('Parameter params must be an array') - if (conditionals.length !== params.length - 1) throw new TypeError('Parameter params must contain exactly one more element than parameter conditionals') - - this.conditionals = conditionals - this.params = params + const operatorMap = { + equal: '==', + unequal: '!=', + smaller: '<', + larger: '>', + smallerEq: '<=', + largerEq: '>=' } - RelationalNode.prototype = new Node() - - RelationalNode.prototype.type = 'RelationalNode' - - RelationalNode.prototype.isRelationalNode = true - - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - RelationalNode.prototype._compile = function (math, argNames) { - const self = this - - const compiled = this.params.map(p => p._compile(math, argNames)) - - return function evalRelationalNode (scope, args, context) { - let evalLhs - let evalRhs = compiled[0](scope, args, context) - - for (let i = 0; i < self.conditionals.length; i++) { - evalLhs = evalRhs - evalRhs = compiled[i + 1](scope, args, context) - const condFn = getSafeProperty(math, self.conditionals[i]) - if (!condFn(evalLhs, evalRhs)) { - return false - } + class RelationalNode extends Node { + /** + * A node representing a chained conditional expression, such as 'x > y > z' + * + * @param {String[]} conditionals + * An array of conditional operators used to compare the parameters + * @param {Node[]} params + * The parameters that will be compared + * + * @constructor RelationalNode + * @extends {Node} + */ + constructor (conditionals, params) { + super() + if (!Array.isArray(conditionals)) { throw new TypeError('Parameter conditionals must be an array') } + if (!Array.isArray(params)) { throw new TypeError('Parameter params must be an array') } + if (conditionals.length !== params.length - 1) { + throw new TypeError( + 'Parameter params must contain exactly one more element ' + + 'than parameter conditionals') } - return true - } - } - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - RelationalNode.prototype.forEach = function (callback) { - this.params.forEach((n, i) => callback(n, 'params[' + i + ']', this), this) - } + this.conditionals = conditionals + this.params = params + } - /** - * Create a new RelationalNode having its childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {RelationalNode} Returns a transformed copy of the node - */ - RelationalNode.prototype.map = function (callback) { - return new RelationalNode(this.conditionals.slice(), this.params.map((n, i) => this._ifNode(callback(n, 'params[' + i + ']', this)), this)) - } + static name = name + get type () { return name } + get isRelationalNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + const self = this + + const compiled = this.params.map(p => p._compile(math, argNames)) + + return function evalRelationalNode (scope, args, context) { + let evalLhs + let evalRhs = compiled[0](scope, args, context) + + for (let i = 0; i < self.conditionals.length; i++) { + evalLhs = evalRhs + evalRhs = compiled[i + 1](scope, args, context) + const condFn = getSafeProperty(math, self.conditionals[i]) + if (!condFn(evalLhs, evalRhs)) { + return false + } + } + return true + } + } - /** - * Create a clone of this node, a shallow copy - * @return {RelationalNode} - */ - RelationalNode.prototype.clone = function () { - return new RelationalNode(this.conditionals, this.params) - } + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + this.params.forEach((n, i) => callback(n, 'params[' + i + ']', this), this) + } - /** - * Get string representation. - * @param {Object} options - * @return {string} str - */ - RelationalNode.prototype._toString = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - const precedence = getPrecedence(this, parenthesis) - - const paramStrings = this.params.map(function (p, index) { - const paramPrecedence = getPrecedence(p, parenthesis) - return (parenthesis === 'all' || (paramPrecedence !== null && paramPrecedence <= precedence)) - ? '(' + p.toString(options) + ')' - : p.toString(options) - }) - - const operatorMap = { - equal: '==', - unequal: '!=', - smaller: '<', - larger: '>', - smallerEq: '<=', - largerEq: '>=' + /** + * Create a new RelationalNode whose children are the results of calling + * the provided callback function for each child of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {RelationalNode} Returns a transformed copy of the node + */ + map (callback) { + return new RelationalNode( + this.conditionals.slice(), + this.params.map( + (n, i) => this._ifNode(callback(n, 'params[' + i + ']', this)), this)) } - let ret = paramStrings[0] - for (let i = 0; i < this.conditionals.length; i++) { - ret += ' ' + operatorMap[this.conditionals[i]] + ' ' + paramStrings[i + 1] + /** + * Create a clone of this node, a shallow copy + * @return {RelationalNode} + */ + clone () { + return new RelationalNode(this.conditionals, this.params) } - return ret - } + /** + * Get string representation. + * @param {Object} options + * @return {string} str + */ + _toString (options) { + const parenthesis = + (options && options.parenthesis) ? options.parenthesis : 'keep' + const precedence = + getPrecedence(this, parenthesis, options && options.implicit) + + const paramStrings = this.params.map(function (p, index) { + const paramPrecedence = + getPrecedence(p, parenthesis, options && options.implicit) + return (parenthesis === 'all' || + (paramPrecedence !== null && paramPrecedence <= precedence)) + ? '(' + p.toString(options) + ')' + : p.toString(options) + }) + + let ret = paramStrings[0] + for (let i = 0; i < this.conditionals.length; i++) { + ret += ' ' + operatorMap[this.conditionals[i]] + ret += ' ' + paramStrings[i + 1] + } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - RelationalNode.prototype.toJSON = function () { - return { - mathjs: 'RelationalNode', - conditionals: this.conditionals, - params: this.params + return ret } - } - - /** - * Instantiate a RelationalNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "RelationalNode", "condition": ..., "trueExpr": ..., "falseExpr": ...}`, - * where mathjs is optional - * @returns {RelationalNode} - */ - RelationalNode.fromJSON = function (json) { - return new RelationalNode(json.conditionals, json.params) - } - /** - * Get HTML representation - * @param {Object} options - * @return {string} str - */ - RelationalNode.prototype.toHTML = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - const precedence = getPrecedence(this, parenthesis) - - const paramStrings = this.params.map(function (p, index) { - const paramPrecedence = getPrecedence(p, parenthesis) - return (parenthesis === 'all' || (paramPrecedence !== null && paramPrecedence <= precedence)) - ? '(' + p.toHTML(options) + ')' - : p.toHTML(options) - }) - - const operatorMap = { - equal: '==', - unequal: '!=', - smaller: '<', - larger: '>', - smallerEq: '<=', - largerEq: '>=' + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { + mathjs: name, + conditionals: this.conditionals, + params: this.params + } } - let ret = paramStrings[0] - for (let i = 0; i < this.conditionals.length; i++) { - ret += '' + escape(operatorMap[this.conditionals[i]]) + '' + paramStrings[i + 1] + /** + * Instantiate a RelationalNode from its JSON representation + * @param {Object} json + * An object structured like + * `{"mathjs": "RelationalNode", "conditionals": ..., "params": ...}`, + * where mathjs is optional + * @returns {RelationalNode} + */ + static fromJSON (json) { + return new RelationalNode(json.conditionals, json.params) } - return ret - } + /** + * Get HTML representation + * @param {Object} options + * @return {string} str + */ + toHTML (options) { + const parenthesis = + (options && options.parenthesis) ? options.parenthesis : 'keep' + const precedence = + getPrecedence(this, parenthesis, options && options.implicit) + + const paramStrings = this.params.map(function (p, index) { + const paramPrecedence = + getPrecedence(p, parenthesis, options && options.implicit) + return (parenthesis === 'all' || + (paramPrecedence !== null && paramPrecedence <= precedence)) + ? ('(' + + p.toHTML(options) + + ')') + : p.toHTML(options) + }) + + let ret = paramStrings[0] + for (let i = 0; i < this.conditionals.length; i++) { + ret += '' + + escape(operatorMap[this.conditionals[i]]) + '' + + paramStrings[i + 1] + } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} str - */ - RelationalNode.prototype._toTex = function (options) { - const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep' - const precedence = getPrecedence(this, parenthesis) - - const paramStrings = this.params.map(function (p, index) { - const paramPrecedence = getPrecedence(p, parenthesis) - return (parenthesis === 'all' || (paramPrecedence !== null && paramPrecedence <= precedence)) - ? '\\left(' + p.toTex(options) + '\right)' - : p.toTex(options) - }) - - let ret = paramStrings[0] - for (let i = 0; i < this.conditionals.length; i++) { - ret += latexOperators[this.conditionals[i]] + paramStrings[i + 1] + return ret } - return ret + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} str + */ + _toTex (options) { + const parenthesis = + (options && options.parenthesis) ? options.parenthesis : 'keep' + const precedence = + getPrecedence(this, parenthesis, options && options.implicit) + + const paramStrings = this.params.map(function (p, index) { + const paramPrecedence = + getPrecedence(p, parenthesis, options && options.implicit) + return (parenthesis === 'all' || + (paramPrecedence !== null && paramPrecedence <= precedence)) + ? '\\left(' + p.toTex(options) + '\right)' + : p.toTex(options) + }) + + let ret = paramStrings[0] + for (let i = 0; i < this.conditionals.length; i++) { + ret += latexOperators[this.conditionals[i]] + paramStrings[i + 1] + } + + return ret + } } return RelationalNode diff --git a/src/expression/node/SymbolNode.js b/src/expression/node/SymbolNode.js index 57b4c098f0..da92197191 100644 --- a/src/expression/node/SymbolNode.js +++ b/src/expression/node/SymbolNode.js @@ -20,181 +20,185 @@ export const createSymbolNode = /* #__PURE__ */ factory(name, dependencies, ({ m return Unit ? Unit.isValuelessUnit(name) : false } - /** - * @constructor SymbolNode - * @extends {Node} - * A symbol node can hold and resolve a symbol - * @param {string} name - * @extends {Node} - */ - function SymbolNode (name) { - if (!(this instanceof SymbolNode)) { - throw new SyntaxError('Constructor must be called with the new operator') - } - - // validate input - if (typeof name !== 'string') throw new TypeError('String expected for parameter "name"') - - this.name = name - } - - SymbolNode.prototype = new Node() - - SymbolNode.prototype.type = 'SymbolNode' + class SymbolNode extends Node { + /** + * @constructor SymbolNode + * @extends {Node} + * A symbol node can hold and resolve a symbol + * @param {string} name + * @extends {Node} + */ + constructor (name) { + super() + // validate input + if (typeof name !== 'string') { + throw new TypeError('String expected for parameter "name"') + } - SymbolNode.prototype.isSymbolNode = true + this.name = name + } - /** - * Compile a node into a JavaScript function. - * This basically pre-calculates as much as possible and only leaves open - * calculations which depend on a dynamic scope with variables. - * @param {Object} math Math.js namespace with functions and constants. - * @param {Object} argNames An object with argument names as key and `true` - * as value. Used in the SymbolNode to optimize - * for arguments from user assigned functions - * (see FunctionAssignmentNode) or special symbols - * like `end` (see IndexNode). - * @return {function} Returns a function which can be called like: - * evalNode(scope: Object, args: Object, context: *) - */ - SymbolNode.prototype._compile = function (math, argNames) { - const name = this.name - - if (argNames[name] === true) { - // this is a FunctionAssignment argument - // (like an x when inside the expression of a function assignment `f(x) = ...`) - return function (scope, args, context) { - return args[name] - } - } else if (name in math) { - return function (scope, args, context) { - return scope.has(name) - ? scope.get(name) - : getSafeProperty(math, name) - } - } else { - const isUnit = isValuelessUnit(name) - - return function (scope, args, context) { - return scope.has(name) - ? scope.get(name) - : isUnit - ? new Unit(null, name) - : SymbolNode.onUndefinedSymbol(name) + get type () { return 'SymbolNode' } + get isSymbolNode () { return true } + + /** + * Compile a node into a JavaScript function. + * This basically pre-calculates as much as possible and only leaves open + * calculations which depend on a dynamic scope with variables. + * @param {Object} math Math.js namespace with functions and constants. + * @param {Object} argNames An object with argument names as key and `true` + * as value. Used in the SymbolNode to optimize + * for arguments from user assigned functions + * (see FunctionAssignmentNode) or special symbols + * like `end` (see IndexNode). + * @return {function} Returns a function which can be called like: + * evalNode(scope: Object, args: Object, context: *) + */ + _compile (math, argNames) { + const name = this.name + + if (argNames[name] === true) { + // this is a FunctionAssignment argument + // (like an x when inside the expression of a function + // assignment `f(x) = ...`) + return function (scope, args, context) { + return args[name] + } + } else if (name in math) { + return function (scope, args, context) { + return scope.has(name) + ? scope.get(name) + : getSafeProperty(math, name) + } + } else { + const isUnit = isValuelessUnit(name) + + return function (scope, args, context) { + return scope.has(name) + ? scope.get(name) + : isUnit + ? new Unit(null, name) + : SymbolNode.onUndefinedSymbol(name) + } } } - } - /** - * Execute a callback for each of the child nodes of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - SymbolNode.prototype.forEach = function (callback) { - // nothing to do, we don't have childs - } - - /** - * Create a new SymbolNode having it's childs be the results of calling - * the provided callback function for each of the childs of the original node. - * @param {function(child: Node, path: string, parent: Node) : Node} callback - * @returns {SymbolNode} Returns a clone of the node - */ - SymbolNode.prototype.map = function (callback) { - return this.clone() - } + /** + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + // nothing to do, we don't have any children + } - /** - * Throws an error 'Undefined symbol {name}' - * @param {string} name - */ - SymbolNode.onUndefinedSymbol = function (name) { - throw new Error('Undefined symbol ' + name) - } + /** + * Create a new SymbolNode with children produced by the given callback. + * Trivial since a SymbolNode has no children + * @param {function(child: Node, path: string, parent: Node) : Node} callback + * @returns {SymbolNode} Returns a clone of the node + */ + map (callback) { + return this.clone() + } - /** - * Create a clone of this node, a shallow copy - * @return {SymbolNode} - */ - SymbolNode.prototype.clone = function () { - return new SymbolNode(this.name) - } + /** + * Throws an error 'Undefined symbol {name}' + * @param {string} name + */ + static onUndefinedSymbol (name) { + throw new Error('Undefined symbol ' + name) + } - /** - * Get string representation - * @param {Object} options - * @return {string} str - * @override - */ - SymbolNode.prototype._toString = function (options) { - return this.name - } + /** + * Create a clone of this node, a shallow copy + * @return {SymbolNode} + */ + clone () { + return new SymbolNode(this.name) + } - /** - * Get HTML representation - * @param {Object} options - * @return {string} str - * @override - */ - SymbolNode.prototype.toHTML = function (options) { - const name = escape(this.name) - - if (name === 'true' || name === 'false') { - return '' + name + '' - } else if (name === 'i') { - return '' + name + '' - } else if (name === 'Infinity') { - return '' + name + '' - } else if (name === 'NaN') { - return '' + name + '' - } else if (name === 'null') { - return '' + name + '' - } else if (name === 'undefined') { - return '' + name + '' + /** + * Get string representation + * @param {Object} options + * @return {string} str + * @override + */ + _toString (options) { + return this.name } - return '' + name + '' - } + /** + * Get HTML representation + * @param {Object} options + * @return {string} str + * @override + */ + toHTML (options) { + const name = escape(this.name) + + if (name === 'true' || name === 'false') { + return '' + name + '' + } else if (name === 'i') { + return '' + + name + '' + } else if (name === 'Infinity') { + return '' + + name + '' + } else if (name === 'NaN') { + return '' + name + '' + } else if (name === 'null') { + return '' + name + '' + } else if (name === 'undefined') { + return '' + + name + '' + } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - SymbolNode.prototype.toJSON = function () { - return { - mathjs: 'SymbolNode', - name: this.name + return '' + name + '' } - } - /** - * Instantiate a SymbolNode from its JSON representation - * @param {Object} json An object structured like - * `{"mathjs": "SymbolNode", name: "x"}`, - * where mathjs is optional - * @returns {SymbolNode} - */ - SymbolNode.fromJSON = function (json) { - return new SymbolNode(json.name) - } + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { + mathjs: 'SymbolNode', + name: this.name + } + } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} str - * @override - */ - SymbolNode.prototype._toTex = function (options) { - let isUnit = false - if ((typeof math[this.name] === 'undefined') && isValuelessUnit(this.name)) { - isUnit = true + /** + * Instantiate a SymbolNode from its JSON representation + * @param {Object} json An object structured like + * `{"mathjs": "SymbolNode", name: "x"}`, + * where mathjs is optional + * @returns {SymbolNode} + */ + static fromJSON (json) { + return new SymbolNode(json.name) } - const symbol = toSymbol(this.name, isUnit) - if (symbol[0] === '\\') { - // no space needed if the symbol starts with '\' - return symbol + + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} str + * @override + */ + _toTex (options) { + let isUnit = false + if ((typeof math[this.name] === 'undefined') && + isValuelessUnit(this.name)) { + isUnit = true + } + const symbol = toSymbol(this.name, isUnit) + if (symbol[0] === '\\') { + // no space needed if the symbol starts with '\' + return symbol + } + // the space prevents symbols from breaking stuff like '\cdot' + // if it's written right before the symbol + return ' ' + symbol } - // the space prevents symbols from breaking stuff like '\cdot' if it's written right before the symbol - return ' ' + symbol } return SymbolNode diff --git a/src/expression/operators.js b/src/expression/operators.js index 4a3e380486..ab73b3de39 100644 --- a/src/expression/operators.js +++ b/src/expression/operators.js @@ -17,6 +17,7 @@ // in parentheses // latexRightParens: the same for the right argument import { hasOwnProperty } from '../utils/object.js' +import { isConstantNode, isParenthesisNode, rule2Node } from '../utils/is.js' export const properties = [ { // assignment @@ -34,6 +35,7 @@ export const properties = [ }, { // logical or 'OperatorNode:or': { + op: 'or', associativity: 'left', associativeWith: [] } @@ -41,56 +43,67 @@ export const properties = [ }, { // logical xor 'OperatorNode:xor': { + op: 'xor', associativity: 'left', associativeWith: [] } }, { // logical and 'OperatorNode:and': { + op: 'and', associativity: 'left', associativeWith: [] } }, { // bitwise or 'OperatorNode:bitOr': { + op: '|', associativity: 'left', associativeWith: [] } }, { // bitwise xor 'OperatorNode:bitXor': { + op: '^|', associativity: 'left', associativeWith: [] } }, { // bitwise and 'OperatorNode:bitAnd': { + op: '&', associativity: 'left', associativeWith: [] } }, { // relational operators 'OperatorNode:equal': { + op: '==', associativity: 'left', associativeWith: [] }, 'OperatorNode:unequal': { + op: '!=', associativity: 'left', associativeWith: [] }, 'OperatorNode:smaller': { + op: '<', associativity: 'left', associativeWith: [] }, 'OperatorNode:larger': { + op: '>', associativity: 'left', associativeWith: [] }, 'OperatorNode:smallerEq': { + op: '<=', associativity: 'left', associativeWith: [] }, 'OperatorNode:largerEq': { + op: '>=', associativity: 'left', associativeWith: [] }, @@ -101,20 +114,24 @@ export const properties = [ }, { // bitshift operators 'OperatorNode:leftShift': { + op: '<<', associativity: 'left', associativeWith: [] }, 'OperatorNode:rightArithShift': { + op: '>>', associativity: 'left', associativeWith: [] }, 'OperatorNode:rightLogShift': { + op: '>>>', associativity: 'left', associativeWith: [] } }, { // unit conversion 'OperatorNode:to': { + op: 'to', associativity: 'left', associativeWith: [] } @@ -124,16 +141,19 @@ export const properties = [ }, { // addition, subtraction 'OperatorNode:add': { + op: '+', associativity: 'left', associativeWith: ['OperatorNode:add', 'OperatorNode:subtract'] }, 'OperatorNode:subtract': { + op: '-', associativity: 'left', associativeWith: [] } }, { // multiply, divide, modulus 'OperatorNode:multiply': { + op: '*', associativity: 'left', associativeWith: [ 'OperatorNode:multiply', @@ -143,6 +163,7 @@ export const properties = [ ] }, 'OperatorNode:divide': { + op: '/', associativity: 'left', associativeWith: [], latexLeftParens: false, @@ -153,6 +174,7 @@ export const properties = [ // in LaTeX }, 'OperatorNode:dotMultiply': { + op: '.*', associativity: 'left', associativeWith: [ 'OperatorNode:multiply', @@ -162,30 +184,48 @@ export const properties = [ ] }, 'OperatorNode:dotDivide': { + op: './', associativity: 'left', associativeWith: [] }, 'OperatorNode:mod': { + op: 'mod', associativity: 'left', associativeWith: [] } }, + { // Repeat multiplication for implicit multiplication + 'OperatorNode:multiply': { + associativity: 'left', + associativeWith: [ + 'OperatorNode:multiply', + 'OperatorNode:divide', + 'Operator:dotMultiply', + 'Operator:dotDivide' + ] + } + }, { // unary prefix operators 'OperatorNode:unaryPlus': { + op: '+', associativity: 'right' }, 'OperatorNode:unaryMinus': { + op: '-', associativity: 'right' }, 'OperatorNode:bitNot': { + op: '~', associativity: 'right' }, 'OperatorNode:not': { + op: 'not', associativity: 'right' } }, { // exponentiation 'OperatorNode:pow': { + op: '^', associativity: 'right', associativeWith: [], latexRightParens: false @@ -194,22 +234,39 @@ export const properties = [ // (it's on top) }, 'OperatorNode:dotPow': { + op: '.^', associativity: 'right', associativeWith: [] } }, { // factorial 'OperatorNode:factorial': { + op: '!', associativity: 'left' } }, { // matrix transpose - 'OperatorNode:transpose': { + 'OperatorNode:ctranspose': { + op: "'", associativity: 'left' } } ] +/** + * Returns the first non-parenthesis internal node, but only + * when the 'parenthesis' option is unset or auto. + * @param {Node} _node + * @param {string} parenthesis + * @return {Node} + */ +function unwrapParen (_node, parenthesis) { + if (!parenthesis || parenthesis !== 'auto') return _node + let node = _node + while (isParenthesisNode(node)) node = node.content + return node +} + /** * Get the precedence of a Node. * Higher number for higher precedence, starting with 0. @@ -217,21 +274,40 @@ export const properties = [ * * @param {Node} _node * @param {string} parenthesis + * @param {string} implicit + * @param {Node} parent (for determining context for implicit multiplication) * @return {number | null} */ -export function getPrecedence (_node, parenthesis) { +export function getPrecedence (_node, parenthesis, implicit, parent) { let node = _node if (parenthesis !== 'keep') { // ParenthesisNodes are only ignored when not in 'keep' mode node = _node.getContent() } const identifier = node.getIdentifier() + let precedence = null for (let i = 0; i < properties.length; i++) { if (identifier in properties[i]) { - return i + precedence = i + break } } - return null + // Bump up precedence of implicit multiplication, except when preceded + // by a "Rule 2" fraction ( [unaryOp]constant / constant ) + if (identifier === 'OperatorNode:multiply' && node.implicit && + implicit !== 'show') { + const leftArg = unwrapParen(node.args[0], parenthesis) + if (!(isConstantNode(leftArg) && parent && + parent.getIdentifier() === 'OperatorNode:divide' && + rule2Node(unwrapParen(parent.args[0], parenthesis))) && + !(leftArg.getIdentifier() === 'OperatorNode:divide' && + rule2Node(unwrapParen(leftArg.args[0], parenthesis)) && + isConstantNode(unwrapParen(leftArg.args[1]))) + ) { + precedence += 1 + } + } + return precedence } /** @@ -309,3 +385,22 @@ export function isAssociativeWith (nodeA, nodeB, parenthesis) { // associativeWith is not defined return null } + +/** + * Get the operator associated with a function name. + * Returns a string with the operator symbol, or null if the + * input is not the name of a function associated with an + * operator. + * + * @param {string} Function name + * @return {string | null} Associated operator symbol, if any + */ +export function getOperator (fn) { + const identifier = 'OperatorNode:' + fn + for (const group of properties) { + if (identifier in group) { + return group[identifier].op + } + } + return null +} diff --git a/src/expression/parse.js b/src/expression/parse.js index 229c39f549..54ce64bfd1 100644 --- a/src/expression/parse.js +++ b/src/expression/parse.js @@ -1,5 +1,5 @@ import { factory } from '../utils/factory.js' -import { isAccessorNode, isConstantNode, isFunctionNode, isOperatorNode, isSymbolNode } from '../utils/is.js' +import { isAccessorNode, isConstantNode, isFunctionNode, isOperatorNode, isSymbolNode, rule2Node } from '../utils/is.js' import { deepMap } from '../utils/collection.js' import { hasOwnProperty } from '../utils/object.js' @@ -613,7 +613,9 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ if (state.token !== '' && state.token !== '\n' && state.token !== ';') { node = parseAssignment(state) - node.comment = state.comment + if (state.comment) { + node.comment = state.comment + } } // TODO: simplify this loop @@ -626,7 +628,9 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ getToken(state) if (state.token !== '\n' && state.token !== ';' && state.token !== '') { node = parseAssignment(state) - node.comment = state.comment + if (state.comment) { + node.comment = state.comment + } visible = (state.token !== ';') blocks.push({ node, visible }) @@ -638,7 +642,9 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ } else { if (!node) { node = new ConstantNode(undefined) - node.comment = state.comment + if (state.comment) { + node.comment = state.comment + } } return node @@ -1075,8 +1081,10 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ /** * Infamous "rule 2" as described in https://github.com/josdejong/mathjs/issues/792#issuecomment-361065370 + * And as amended in https://github.com/josdejong/mathjs/issues/2370#issuecomment-1054052164 * Explicit division gets higher precedence than implicit multiplication - * when the division matches this pattern: [number] / [number] [symbol] + * when the division matches this pattern: + * [unaryPrefixOp]?[number] / [number] [symbol] * @return {Node} node * @private */ @@ -1087,7 +1095,7 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ while (true) { // Match the "number /" part of the pattern "number / number symbol" - if (state.token === '/' && isConstantNode(last)) { + if (state.token === '/' && rule2Node(last)) { // Look ahead to see if the next token is a number tokenStates.push(Object.assign({}, state)) getTokenSkipNewline(state) @@ -1783,5 +1791,8 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ return error } + // Now that we can parse, automatically convert strings to Nodes by parsing + typed.addConversion({ from: 'string', to: 'Node', convert: parse }) + return parse }) diff --git a/src/expression/transform/std.transform.js b/src/expression/transform/std.transform.js index 4f2988c09f..6c92efdad4 100644 --- a/src/expression/transform/std.transform.js +++ b/src/expression/transform/std.transform.js @@ -4,7 +4,7 @@ import { errorTransform } from './utils/errorTransform.js' import { lastDimToZeroBase } from './utils/lastDimToZeroBase.js' const name = 'std' -const dependencies = ['typed', 'sqrt', 'variance'] +const dependencies = ['typed', 'map', 'sqrt', 'variance'] /** * Attach a transform function to math.std @@ -13,8 +13,8 @@ const dependencies = ['typed', 'sqrt', 'variance'] * This transform changed the `dim` parameter of function std * from one-based to zero based */ -export const createStdTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, sqrt, variance }) => { - const std = createStd({ typed, sqrt, variance }) +export const createStdTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, map, sqrt, variance }) => { + const std = createStd({ typed, map, sqrt, variance }) return typed('std', { '...any': function (args) { diff --git a/src/factoriesAny.js b/src/factoriesAny.js index 6ff5739af3..2ffc291c23 100644 --- a/src/factoriesAny.js +++ b/src/factoriesAny.js @@ -248,6 +248,7 @@ export { createCatalan } from './function/combinatorics/catalan.js' export { createComposition } from './function/combinatorics/composition.js' export { createLeafCount } from './function/algebra/leafCount.js' export { createSimplify } from './function/algebra/simplify.js' +export { createSimplifyConstant } from './function/algebra/simplifyConstant.js' export { createSimplifyCore } from './function/algebra/simplifyCore.js' export { createResolve } from './function/algebra/resolve.js' export { createSymbolicEqual } from './function/algebra/symbolicEqual.js' diff --git a/src/factoriesNumber.js b/src/factoriesNumber.js index 6531170363..9ae85a9b40 100644 --- a/src/factoriesNumber.js +++ b/src/factoriesNumber.js @@ -91,6 +91,7 @@ export { createChain } from './type/chain/function/chain.js' // algebra export { createResolve } from './function/algebra/resolve.js' export { createSimplify } from './function/algebra/simplify.js' +export { createSimplifyConstant } from './function/algebra/simplifyConstant.js' export { createSimplifyCore } from './function/algebra/simplifyCore.js' export { createDerivative } from './function/algebra/derivative.js' export { createRationalize } from './function/algebra/rationalize.js' diff --git a/src/function/algebra/derivative.js b/src/function/algebra/derivative.js index b6b9b491e8..505e8ef5b5 100644 --- a/src/function/algebra/derivative.js +++ b/src/function/algebra/derivative.js @@ -69,39 +69,19 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ * be simplified. * @return {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} The derivative of `expr` */ - const derivative = typed('derivative', { - 'Node, SymbolNode, Object': function (expr, variable, options) { - const constNodes = {} - constTag(constNodes, expr, variable.name) - const res = _derivative(expr, constNodes) - return options.simplify ? simplify(res) : res - }, - 'Node, SymbolNode': function (expr, variable) { - return this(expr, variable, { simplify: true }) - }, - - 'string, SymbolNode': function (expr, variable) { - return this(parse(expr), variable) - }, - 'string, SymbolNode, Object': function (expr, variable, options) { - return this(parse(expr), variable, options) - }, - - 'string, string': function (expr, variable) { - return this(parse(expr), parse(variable)) - }, - 'string, string, Object': function (expr, variable, options) { - return this(parse(expr), parse(variable), options) - }, + function plainDerivative (expr, variable, options = { simplify: true }) { + const constNodes = {} + constTag(constNodes, expr, variable.name) + const res = _derivative(expr, constNodes) + return options.simplify ? simplify(res) : res + } - 'Node, string': function (expr, variable) { - return this(expr, parse(variable)) - }, - 'Node, string, Object': function (expr, variable, options) { - return this(expr, parse(variable), options) - } + typed.addConversion( + { from: 'identifier', to: 'SymbolNode', convert: parse }) - // TODO: replace the 8 signatures above with 4 as soon as typed-function supports optional arguments + const derivative = typed(name, { + 'Node, SymbolNode': plainDerivative, + 'Node, SymbolNode, Object': plainDerivative /* TODO: implement and test syntax with order of derivatives -> implement as an option {order: number} 'Node, SymbolNode, ConstantNode': function (expr, variable, {order}) { @@ -116,6 +96,9 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ */ }) + typed.removeConversion( + { from: 'identifier', to: 'SymbolNode', convert: parse }) + derivative._simplify = true derivative.toTex = function (deriv) { diff --git a/src/function/algebra/leafCount.js b/src/function/algebra/leafCount.js index f3f7ee8a2c..0aefa277de 100644 --- a/src/function/algebra/leafCount.js +++ b/src/function/algebra/leafCount.js @@ -49,9 +49,6 @@ export const createLeafCount = /* #__PURE__ */ factory(name, dependencies, ({ * */ return typed(name, { - string: function (expr) { - return this(parse(expr)) - }, Node: function (expr) { return countLeaves(expr) } diff --git a/src/function/algebra/rationalize.js b/src/function/algebra/rationalize.js index 3a02024887..5c560e4411 100644 --- a/src/function/algebra/rationalize.js +++ b/src/function/algebra/rationalize.js @@ -1,6 +1,5 @@ import { isInteger } from '../../utils/number.js' import { factory } from '../../utils/factory.js' -import { createSimplifyConstant } from './simplify/simplifyConstant.js' const name = 'rationalize' const dependencies = [ @@ -14,6 +13,7 @@ const dependencies = [ 'divide', 'pow', 'parse', + 'simplifyConstant', 'simplifyCore', 'simplify', '?bignumber', @@ -42,6 +42,7 @@ export const createRationalize = /* #__PURE__ */ factory(name, dependencies, ({ divide, pow, parse, + simplifyConstant, simplifyCore, simplify, fraction, @@ -58,23 +59,6 @@ export const createRationalize = /* #__PURE__ */ factory(name, dependencies, ({ SymbolNode, ParenthesisNode }) => { - const simplifyConstant = createSimplifyConstant({ - typed, - config, - mathWithTransform, - matrix, - fraction, - bignumber, - AccessorNode, - ArrayNode, - ConstantNode, - FunctionNode, - IndexNode, - ObjectNode, - OperatorNode, - SymbolNode - }) - /** * Transform a rationalizable expression in a rational fraction. * If rational fraction is one variable polynomial then converts @@ -128,104 +112,81 @@ export const createRationalize = /* #__PURE__ */ factory(name, dependencies, ({ * {Expression Node} node simplified expression * */ - return typed(name, { - string: function (expr) { - return this(parse(expr), {}, false) - }, - - 'string, boolean': function (expr, detailed) { - return this(parse(expr), {}, detailed) - }, - - 'string, Object': function (expr, scope) { - return this(parse(expr), scope, false) - }, - - 'string, Object, boolean': function (expr, scope, detailed) { - return this(parse(expr), scope, detailed) - }, - - Node: function (expr) { - return this(expr, {}, false) - }, - - 'Node, boolean': function (expr, detailed) { - return this(expr, {}, detailed) - }, - - 'Node, Object': function (expr, scope) { - return this(expr, scope, false) - }, - - 'Node, Object, boolean': function (expr, scope, detailed) { - const setRules = rulesRationalize() // Rules for change polynomial in near canonical form - const polyRet = polynomial(expr, scope, true, setRules.firstRules) // Check if expression is a rationalizable polynomial - const nVars = polyRet.variables.length - const noExactFractions = { exactFractions: false } - const withExactFractions = { exactFractions: true } - expr = polyRet.expression - - if (nVars >= 1) { // If expression in not a constant - expr = expandPower(expr) // First expand power of polynomials (cannot be made from rules!) - let sBefore // Previous expression - let rules - let eDistrDiv = true - let redoInic = false - // Apply the initial rules, including succ div rules: - expr = simplify(expr, setRules.firstRules, {}, noExactFractions) - let s - while (true) { - // Alternate applying successive division rules and distr.div.rules - // until there are no more changes: - rules = eDistrDiv ? setRules.distrDivRules : setRules.sucDivRules - expr = simplify(expr, rules, {}, withExactFractions) - eDistrDiv = !eDistrDiv // Swap between Distr.Div and Succ. Div. Rules - - s = expr.toString() - if (s === sBefore) { - break // No changes : end of the loop - } - - redoInic = true - sBefore = s + function _rationalize (expr, scope = {}, detailed = false) { + const setRules = rulesRationalize() // Rules for change polynomial in near canonical form + const polyRet = polynomial(expr, scope, true, setRules.firstRules) // Check if expression is a rationalizable polynomial + const nVars = polyRet.variables.length + const noExactFractions = { exactFractions: false } + const withExactFractions = { exactFractions: true } + expr = polyRet.expression + + if (nVars >= 1) { // If expression in not a constant + expr = expandPower(expr) // First expand power of polynomials (cannot be made from rules!) + let sBefore // Previous expression + let rules + let eDistrDiv = true + let redoInic = false + // Apply the initial rules, including succ div rules: + expr = simplify(expr, setRules.firstRules, {}, noExactFractions) + let s + while (true) { + // Alternate applying successive division rules and distr.div.rules + // until there are no more changes: + rules = eDistrDiv ? setRules.distrDivRules : setRules.sucDivRules + expr = simplify(expr, rules, {}, withExactFractions) + eDistrDiv = !eDistrDiv // Swap between Distr.Div and Succ. Div. Rules + + s = expr.toString() + if (s === sBefore) { + break // No changes : end of the loop } - if (redoInic) { // Apply first rules again without succ div rules (if there are changes) - expr = simplify(expr, setRules.firstRulesAgain, {}, noExactFractions) - } - // Apply final rules: - expr = simplify(expr, setRules.finalRules, {}, noExactFractions) - } // NVars >= 1 + redoInic = true + sBefore = s + } - const coefficients = [] - const retRationalize = {} + if (redoInic) { // Apply first rules again without succ div rules (if there are changes) + expr = simplify(expr, setRules.firstRulesAgain, {}, noExactFractions) + } + // Apply final rules: + expr = simplify(expr, setRules.finalRules, {}, noExactFractions) + } // NVars >= 1 - if (expr.type === 'OperatorNode' && expr.isBinary() && expr.op === '/') { // Separate numerator from denominator - if (nVars === 1) { - expr.args[0] = polyToCanonical(expr.args[0], coefficients) - expr.args[1] = polyToCanonical(expr.args[1]) - } - if (detailed) { - retRationalize.numerator = expr.args[0] - retRationalize.denominator = expr.args[1] - } - } else { - if (nVars === 1) { - expr = polyToCanonical(expr, coefficients) - } - if (detailed) { - retRationalize.numerator = expr - retRationalize.denominator = null - } + const coefficients = [] + const retRationalize = {} + + if (expr.type === 'OperatorNode' && expr.isBinary() && expr.op === '/') { // Separate numerator from denominator + if (nVars === 1) { + expr.args[0] = polyToCanonical(expr.args[0], coefficients) + expr.args[1] = polyToCanonical(expr.args[1]) + } + if (detailed) { + retRationalize.numerator = expr.args[0] + retRationalize.denominator = expr.args[1] + } + } else { + if (nVars === 1) { + expr = polyToCanonical(expr, coefficients) + } + if (detailed) { + retRationalize.numerator = expr + retRationalize.denominator = null } - // nVars - - if (!detailed) return expr - retRationalize.coefficients = coefficients - retRationalize.variables = polyRet.variables - retRationalize.expression = expr - return retRationalize - } // ^^^^^^^ end of rationalize ^^^^^^^^ + } + // nVars + + if (!detailed) return expr + retRationalize.coefficients = coefficients + retRationalize.variables = polyRet.variables + retRationalize.expression = expr + return retRationalize + } + + return typed(name, { + Node: _rationalize, + 'Node, boolean': (expr, detailed) => _rationalize(expr, {}, detailed), + 'Node, Object': _rationalize, + 'Node, Object, boolean': _rationalize }) // end of typed rationalize /** diff --git a/src/function/algebra/resolve.js b/src/function/algebra/resolve.js index 1f2373baa4..5e8f80f155 100644 --- a/src/function/algebra/resolve.js +++ b/src/function/algebra/resolve.js @@ -1,9 +1,10 @@ -import { createMap, isMap } from '../../utils/map.js' +import { createMap } from '../../utils/map.js' import { isFunctionNode, isNode, isOperatorNode, isParenthesisNode, isSymbolNode } from '../../utils/is.js' import { factory } from '../../utils/factory.js' const name = 'resolve' const dependencies = [ + 'typed', 'parse', 'ConstantNode', 'FunctionNode', @@ -12,6 +13,7 @@ const dependencies = [ ] export const createResolve = /* #__PURE__ */ factory(name, dependencies, ({ + typed, parse, ConstantNode, FunctionNode, @@ -27,9 +29,9 @@ export const createResolve = /* #__PURE__ */ factory(name, dependencies, ({ * * Examples: * - * math.resolve('x + y', {x:1, y:2}) // Node {1 + 2} - * math.resolve(math.parse('x+y'), {x:1, y:2}) // Node {1 + 2} - * math.simplify('x+y', {x:2, y:'x+x'}).toString() // "6" + * math.resolve('x + y', {x:1, y:2}) // Node '1 + 2' + * math.resolve(math.parse('x+y'), {x:1, y:2}) // Node '1 + 2' + * math.simplify('x+y', {x:2, y: math.parse('x+x')}).toString() // "6" * * See also: * @@ -44,15 +46,12 @@ export const createResolve = /* #__PURE__ */ factory(name, dependencies, ({ * If there is a cyclic dependency among the variables in `scope`, * resolution is impossible and a ReferenceError is thrown. */ - function resolve (node, scope, within = new Set()) { // note `within`: + function _resolve (node, scope, within = new Set()) { // note `within`: // `within` is not documented, since it is for internal cycle // detection only if (!scope) { return node } - if (!isMap(scope)) { - scope = createMap(scope) - } if (isSymbolNode(node)) { if (within.has(node.name)) { const variables = Array.from(within).join(', ') @@ -64,7 +63,7 @@ export const createResolve = /* #__PURE__ */ factory(name, dependencies, ({ if (isNode(value)) { const nextWithin = new Set(within) nextWithin.add(node.name) - return resolve(value, scope, nextWithin) + return _resolve(value, scope, nextWithin) } else if (typeof value === 'number') { return parse(String(value)) } else if (value !== undefined) { @@ -74,22 +73,38 @@ export const createResolve = /* #__PURE__ */ factory(name, dependencies, ({ } } else if (isOperatorNode(node)) { const args = node.args.map(function (arg) { - return resolve(arg, scope, within) + return _resolve(arg, scope, within) }) return new OperatorNode(node.op, node.fn, args, node.implicit) } else if (isParenthesisNode(node)) { - return new ParenthesisNode(resolve(node.content, scope, within)) + return new ParenthesisNode(_resolve(node.content, scope, within)) } else if (isFunctionNode(node)) { const args = node.args.map(function (arg) { - return resolve(arg, scope, within) + return _resolve(arg, scope, within) }) return new FunctionNode(node.name, args) } // Otherwise just recursively resolve any children (might also work // for some of the above special cases) - return node.map(child => resolve(child, scope, within)) + return node.map(child => _resolve(child, scope, within)) } - return resolve + return typed('resolve', { + Node: _resolve, + 'Node, Map | null | undefined': _resolve, + 'Node, Object': (n, scope) => _resolve(n, createMap(scope)), + // For arrays and matrices, we map `self` rather than `_resolve` + // because resolve is fairly expensive anyway, and this way + // we get nice error messages if one entry in the array has wrong type. + 'Array | Matrix': typed.referToSelf(self => A => A.map(n => self(n))), + 'Array | Matrix, null | undefined': typed.referToSelf( + self => A => A.map(n => self(n))), + 'Array, Object': typed.referTo( + 'Array,Map', selfAM => (A, scope) => selfAM(A, createMap(scope))), + 'Matrix, Object': typed.referTo( + 'Matrix,Map', selfMM => (A, scope) => selfMM(A, createMap(scope))), + 'Array | Matrix, Map': typed.referToSelf( + self => (A, scope) => A.map(n => self(n, scope))) + }) }) diff --git a/src/function/algebra/simplify.js b/src/function/algebra/simplify.js index ee18b2eeed..732b116f48 100644 --- a/src/function/algebra/simplify.js +++ b/src/function/algebra/simplify.js @@ -1,7 +1,6 @@ import { isConstantNode, isParenthesisNode } from '../../utils/is.js' import { factory } from '../../utils/factory.js' import { createUtil } from './simplify/util.js' -import { createSimplifyConstant } from './simplify/simplifyConstant.js' import { hasOwnProperty } from '../../utils/object.js' import { createEmptyMap, createMap } from '../../utils/map.js' @@ -18,6 +17,7 @@ const dependencies = [ 'isZero', 'equal', 'resolve', + 'simplifyConstant', 'simplifyCore', '?fraction', '?bignumber', @@ -47,6 +47,7 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, ( isZero, equal, resolve, + simplifyConstant, simplifyCore, fraction, bignumber, @@ -63,23 +64,6 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, ( SymbolNode } ) => { - const simplifyConstant = createSimplifyConstant({ - typed, - config, - mathWithTransform, - matrix, - fraction, - bignumber, - AccessorNode, - ArrayNode, - ConstantNode, - FunctionNode, - IndexNode, - ObjectNode, - OperatorNode, - SymbolNode - }) - const { hasProperty, isCommutative, isAssociative, mergeContext, flatten, unflattenr, unflattenl, createMakeNodeFunction, defaultContext, realContext, positiveContext } = createUtil({ FunctionNode, OperatorNode, SymbolNode }) @@ -111,7 +95,10 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, ( * - 'v' - matches any Node that is not a ConstantNode * * The default list of rules is exposed on the function as `simplify.rules` - * and can be used as a basis to built a set of custom rules. + * and can be used as a basis to built a set of custom rules. Note that since + * the `simplifyCore` function is in the default list of rules, by default + * simplify will convert any function calls in the expression that have + * operator equivalents to their operator forms. * * To specify a rule as a string, separate the left and right pattern by '->' * When specifying a rule as an object, the following keys are meaningful: @@ -186,96 +173,18 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, ( * Optional list with custom rules * @return {Node} Returns the simplified form of `expr` */ + typed.addConversion({ from: 'Object', to: 'Map', convert: createMap }) const simplify = typed('simplify', { - string: function (expr) { - return this(parse(expr), this.rules, createEmptyMap(), {}) - }, - - 'string, Map | Object': function (expr, scope) { - return this(parse(expr), this.rules, scope, {}) - }, - - 'string, Map | Object, Object': function (expr, scope, options) { - return this(parse(expr), this.rules, scope, options) - }, - - 'string, Array': function (expr, rules) { - return this(parse(expr), rules, createEmptyMap(), {}) - }, - - 'string, Array, Map | Object': function (expr, rules, scope) { - return this(parse(expr), rules, scope, {}) - }, - - 'string, Array, Map | Object, Object': function (expr, rules, scope, options) { - return this(parse(expr), rules, scope, options) - }, - - 'Node, Map | Object': function (expr, scope) { - return this(expr, this.rules, scope, {}) - }, - - 'Node, Map | Object, Object': function (expr, scope, options) { - return this(expr, this.rules, scope, options) - }, - - Node: function (expr) { - return this(expr, this.rules, createEmptyMap(), {}) - }, - - 'Node, Array': function (expr, rules) { - return this(expr, rules, createEmptyMap(), {}) - }, - - 'Node, Array, Map | Object': function (expr, rules, scope) { - return this(expr, rules, scope, {}) - }, - - 'Node, Array, Object, Object': function (expr, rules, scope, options) { - return this(expr, rules, createMap(scope), options) - }, - - 'Node, Array, Map, Object': function (expr, rules, scope, options) { - const debug = options.consoleDebug - rules = _buildRules(rules, options.context) - let res = resolve(expr, scope) - res = removeParens(res) - const visited = {} - let str = res.toString({ parenthesis: 'all' }) - while (!visited[str]) { - visited[str] = true - _lastsym = 0 // counter for placeholder symbols - let laststr = str - if (debug) console.log('Working on: ', str) - for (let i = 0; i < rules.length; i++) { - let rulestr = '' - if (typeof rules[i] === 'function') { - res = rules[i](res, options) - if (debug) rulestr = rules[i].name - } else { - flatten(res, options.context) - res = applyRule(res, rules[i], options.context) - if (debug) { - rulestr = `${rules[i].l.toString()} -> ${rules[i].r.toString()}` - } - } - if (debug) { - const newstr = res.toString({ parenthesis: 'all' }) - if (newstr !== laststr) { - console.log('Applying', rulestr, 'produced', newstr) - laststr = newstr - } - } - /* Use left-heavy binary tree internally, - * since custom rule functions may expect it - */ - unflattenl(res, options.context) - } - str = res.toString({ parenthesis: 'all' }) - } - return res - } + Node: _simplify, + 'Node, Map': (expr, scope) => _simplify(expr, false, scope), + 'Node, Map, Object': + (expr, scope, options) => _simplify(expr, false, scope, options), + 'Node, Array': _simplify, + 'Node, Array, Map': _simplify, + 'Node, Array, Map, Object': _simplify }) + typed.removeConversion({ from: 'Object', to: 'Map', convert: createMap }) + simplify.defaultContext = defaultContext simplify.realContext = realContext simplify.positiveContext = positiveContext @@ -591,6 +500,47 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, ( return new SymbolNode('_p' + _lastsym++) } + function _simplify (expr, rules, scope = createEmptyMap(), options = {}) { + const debug = options.consoleDebug + rules = _buildRules(rules || simplify.rules, options.context) + let res = resolve(expr, scope) + res = removeParens(res) + const visited = {} + let str = res.toString({ parenthesis: 'all' }) + while (!visited[str]) { + visited[str] = true + _lastsym = 0 // counter for placeholder symbols + let laststr = str + if (debug) console.log('Working on: ', str) + for (let i = 0; i < rules.length; i++) { + let rulestr = '' + if (typeof rules[i] === 'function') { + res = rules[i](res, options) + if (debug) rulestr = rules[i].name + } else { + flatten(res, options.context) + res = applyRule(res, rules[i], options.context) + if (debug) { + rulestr = `${rules[i].l.toString()} -> ${rules[i].r.toString()}` + } + } + if (debug) { + const newstr = res.toString({ parenthesis: 'all' }) + if (newstr !== laststr) { + console.log('Applying', rulestr, 'produced', newstr) + laststr = newstr + } + } + /* Use left-heavy binary tree internally, + * since custom rule functions may expect it + */ + unflattenl(res, options.context) + } + str = res.toString({ parenthesis: 'all' }) + } + return res + } + function mapRule (nodes, rule, context) { let resNodes = nodes if (nodes) { diff --git a/src/function/algebra/simplify/simplifyConstant.js b/src/function/algebra/simplifyConstant.js similarity index 90% rename from src/function/algebra/simplify/simplifyConstant.js rename to src/function/algebra/simplifyConstant.js index 577b0f8c32..b04bfcd3ec 100644 --- a/src/function/algebra/simplify/simplifyConstant.js +++ b/src/function/algebra/simplifyConstant.js @@ -1,12 +1,12 @@ -// TODO this could be improved by simplifying seperated constants under associative and commutative operators -import { isFraction, isMatrix, isNode, isArrayNode, isConstantNode, isIndexNode, isObjectNode, isOperatorNode } from '../../../utils/is.js' -import { factory } from '../../../utils/factory.js' -import { createUtil } from './util.js' -import { noBignumber, noFraction } from '../../../utils/noop.js' +import { isFraction, isMatrix, isNode, isArrayNode, isConstantNode, isIndexNode, isObjectNode, isOperatorNode } from '../../utils/is.js' +import { factory } from '../../utils/factory.js' +import { createUtil } from './simplify/util.js' +import { noBignumber, noFraction } from '../../utils/noop.js' const name = 'simplifyConstant' const dependencies = [ 'typed', + 'parse', 'config', 'mathWithTransform', 'matrix', @@ -24,6 +24,7 @@ const dependencies = [ export const createSimplifyConstant = /* #__PURE__ */ factory(name, dependencies, ({ typed, + parse, config, mathWithTransform, matrix, @@ -41,9 +42,40 @@ export const createSimplifyConstant = /* #__PURE__ */ factory(name, dependencies const { isCommutative, isAssociative, allChildren, createMakeNodeFunction } = createUtil({ FunctionNode, OperatorNode, SymbolNode }) - function simplifyConstant (expr, options) { - return _ensureNode(foldFraction(expr, options)) - } + /** + * simplifyConstant() takes a mathjs expression (either a Node representing + * a parse tree or a string which it parses to produce a node), and replaces + * any subexpression of it consisting entirely of constants with the computed + * value of that subexpression. + * + * Syntax: + * + * simplifyConstant(expr) + * simplifyConstant(expr, options) + * + * Examples: + * + * math.simplifyConstant('x + 4*3/6') // Node "x + 2" + * math.simplifyConstant('z cos(0)') // Node "z 1" + * math.simplifyConstant('(5.2 + 1.08)t', {exactFractions: false}) // Node "6.28 t" + * + * See also: + * + * simplify, simplifyCore, resolve, derivative + * + * @param {Node | string} node + * The expression to be simplified + * @param {Object} options + * Simplification options, as per simplify() + * @return {Node} Returns expression with constant subexpressions evaluated + */ + const simplifyConstant = typed('simplifyConstant', { + Node: node => _ensureNode(foldFraction(node, {})), + + 'Node, Object': function (expr, options) { + return _ensureNode(foldFraction(expr, options)) + } + }) function _removeFractions (thing) { if (isFraction(thing)) { diff --git a/src/function/algebra/simplifyCore.js b/src/function/algebra/simplifyCore.js index ffbdf3aa61..9a7c1b87e5 100644 --- a/src/function/algebra/simplifyCore.js +++ b/src/function/algebra/simplifyCore.js @@ -1,9 +1,12 @@ import { isAccessorNode, isArrayNode, isConstantNode, isFunctionNode, isIndexNode, isObjectNode, isOperatorNode } from '../../utils/is.js' +import { getOperator } from '../../expression/operators.js' import { createUtil } from './simplify/util.js' import { factory } from '../../utils/factory.js' const name = 'simplifyCore' const dependencies = [ + 'typed', + 'parse', 'equal', 'isZero', 'add', @@ -23,6 +26,8 @@ const dependencies = [ ] export const createSimplifyCore = /* #__PURE__ */ factory(name, dependencies, ({ + typed, + parse, equal, isZero, add, @@ -42,115 +47,167 @@ export const createSimplifyCore = /* #__PURE__ */ factory(name, dependencies, ({ }) => { const node0 = new ConstantNode(0) const node1 = new ConstantNode(1) + const nodeT = new ConstantNode(true) + const nodeF = new ConstantNode(false) + // test if a node will always have a boolean value (true/false) + // not sure if this list is complete + function isAlwaysBoolean (node) { + return isOperatorNode(node) && ['and', 'not', 'or'].includes(node.op) + } const { hasProperty, isCommutative } = createUtil({ FunctionNode, OperatorNode, SymbolNode }) /** * simplifyCore() performs single pass simplification suitable for - * applications requiring ultimate performance. In contrast, simplify() - * extends simplifyCore() with additional passes to provide deeper - * simplification. + * applications requiring ultimate performance. To roughly summarize, + * it handles cases along the lines of simplifyConstant() but where + * knowledge of a single argument is sufficient to determine the value. + * In contrast, simplify() extends simplifyCore() with additional passes + * to provide deeper simplification (such as gathering like terms). + * + * Specifically, simplifyCore: + * + * * Converts all function calls with operator equivalents to their + * operator forms. + * * Removes operators or function calls that are guaranteed to have no + * effect (such as unary '+'). + * * Removes double unary '-', '~', and 'not' + * * Eliminates addition/subtraction of 0 and multiplication/division/powers + * by 1 or 0. + * * Converts addition of a negation into subtraction. + * * Eliminates logical operations with constant true or false leading + * arguments. + * * Puts constants on the left of a product, if multiplication is + * considered commutative by the options (which is the default) * * Syntax: * * simplifyCore(expr) + * simplifyCore(expr, options) * * Examples: * - * const f = math.parse('2 * 1 * x ^ (2 - 1)') - * math.simpifyCore(f) // Node {2 * x} - * math.simplify('2 * 1 * x ^ (2 - 1)', [math.simplifyCore]) // Node {2 * x} + * const f = math.parse('2 * 1 * x ^ (1 - 0)') + * math.simplifyCore(f) // Node "2 * x" + * math.simplify('2 * 1 * x ^ (1 - 0)', [math.simplifyCore]) // Node "2 * x" * * See also: * - * simplify, resolve, derivative + * simplify, simplifyConstant, resolve, derivative * - * @param {Node} node + * @param {Node | string} node * The expression to be simplified * @param {Object} options * Simplification options, as per simplify() * @return {Node} Returns expression with basic simplifications applied */ - function simplifyCore (node, options) { + function _simplifyCore (nodeToSimplify, options = {}) { const context = options ? options.context : undefined - if (hasProperty(node, 'trivial', context)) { + if (hasProperty(nodeToSimplify, 'trivial', context)) { // This node does nothing if it has only one argument, so if so, // return that argument simplified - if (isFunctionNode(node) && node.args.length === 1) { - return simplifyCore(node.args[0], options) + if (isFunctionNode(nodeToSimplify) && nodeToSimplify.args.length === 1) { + return _simplifyCore(nodeToSimplify.args[0], options) } // For other node types, we try the generic methods let simpChild = false let childCount = 0 - node.forEach(c => { + nodeToSimplify.forEach(c => { ++childCount if (childCount === 1) { - simpChild = simplifyCore(c, options) + simpChild = _simplifyCore(c, options) } }) if (childCount === 1) { return simpChild } } + let node = nodeToSimplify + if (isFunctionNode(node)) { + const op = getOperator(node.name) + if (op) { + // Replace FunctionNode with a new OperatorNode + if (node.args.length > 2 && hasProperty(node, 'associative', context)) { + // unflatten into binary operations since that's what simplifyCore handles + while (node.args.length > 2) { + const last = node.args.pop() + const seclast = node.args.pop() + node.args.push(new OperatorNode(op, node.name, [last, seclast])) + } + } + node = new OperatorNode(op, node.name, node.args) + } else { + return new FunctionNode( + _simplifyCore(node.fn), node.args.map(n => _simplifyCore(n, options))) + } + } if (isOperatorNode(node) && node.isUnary()) { - const a0 = simplifyCore(node.args[0], options) + const a0 = _simplifyCore(node.args[0], options) + if (node.op === '~') { // bitwise not + if (isOperatorNode(a0) && a0.isUnary() && a0.op === '~') { + return a0.args[0] + } + } + if (node.op === 'not') { // logical not + if (isOperatorNode(a0) && a0.isUnary() && a0.op === 'not') { + // Has the effect of turning the argument into a boolean + // So can only eliminate the double negation if + // the inside is already boolean + if (isAlwaysBoolean(a0.args[0])) { + return a0.args[0] + } + } + } + let finish = true if (node.op === '-') { // unary minus if (isOperatorNode(a0)) { + if (a0.isBinary() && a0.fn === 'subtract') { + node = new OperatorNode('-', 'subtract', [a0.args[1], a0.args[0]]) + finish = false // continue to process the new binary node + } if (a0.isUnary() && a0.op === '-') { return a0.args[0] - } else if (a0.isBinary() && a0.fn === 'subtract') { - return new OperatorNode('-', 'subtract', [a0.args[1], a0.args[0]]) } } - return new OperatorNode(node.op, node.fn, [a0]) } - } else if (isOperatorNode(node) && node.isBinary()) { - const a0 = simplifyCore(node.args[0], options) - const a1 = simplifyCore(node.args[1], options) + if (finish) return new OperatorNode(node.op, node.fn, [a0]) + } + if (isOperatorNode(node) && node.isBinary()) { + const a0 = _simplifyCore(node.args[0], options) + let a1 = _simplifyCore(node.args[1], options) if (node.op === '+') { - if (isConstantNode(a0)) { - if (isZero(a0.value)) { - return a1 - } else if (isConstantNode(a1)) { - return new ConstantNode(add(a0.value, a1.value)) - } + if (isConstantNode(a0) && isZero(a0.value)) { + return a1 } if (isConstantNode(a1) && isZero(a1.value)) { return a0 } if (isOperatorNode(a1) && a1.isUnary() && a1.op === '-') { - return new OperatorNode('-', 'subtract', [a0, a1.args[0]]) - } - return new OperatorNode(node.op, node.fn, a1 ? [a0, a1] : [a0]) - } else if (node.op === '-') { - if (isConstantNode(a0) && a1) { - if (isConstantNode(a1)) { - return new ConstantNode(subtract(a0.value, a1.value)) - } else if (isZero(a0.value)) { - return new OperatorNode('-', 'unaryMinus', [a1]) - } + a1 = a1.args[0] + node = new OperatorNode('-', 'subtract', [a0, a1]) } - // if (node.fn === "subtract" && node.args.length === 2) { - if (node.fn === 'subtract') { - if (isConstantNode(a1) && isZero(a1.value)) { - return a0 - } - if (isOperatorNode(a1) && a1.isUnary() && a1.op === '-') { - return simplifyCore( - new OperatorNode('+', 'add', [a0, a1.args[0]]), options) - } - return new OperatorNode(node.op, node.fn, [a0, a1]) + } + if (node.op === '-') { + if (isOperatorNode(a1) && a1.isUnary() && a1.op === '-') { + return _simplifyCore( + new OperatorNode('+', 'add', [a0, a1.args[0]]), options) + } + if (isConstantNode(a0) && isZero(a0.value)) { + return _simplifyCore(new OperatorNode('-', 'unaryMinus', [a1])) } - } else if (node.op === '*') { + if (isConstantNode(a1) && isZero(a1.value)) { + return a0 + } + return new OperatorNode(node.op, node.fn, [a0, a1]) + } + if (node.op === '*') { if (isConstantNode(a0)) { if (isZero(a0.value)) { return node0 } else if (equal(a0.value, 1)) { return a1 - } else if (isConstantNode(a1)) { - return new ConstantNode(multiply(a0.value, a1.value)) } } if (isConstantNode(a1)) { @@ -158,77 +215,90 @@ export const createSimplifyCore = /* #__PURE__ */ factory(name, dependencies, ({ return node0 } else if (equal(a1.value, 1)) { return a0 - } else if (isOperatorNode(a0) && a0.isBinary() && - a0.op === node.op && isCommutative(node, context)) { - const a00 = a0.args[0] - if (isConstantNode(a00)) { - const a00a1 = new ConstantNode(multiply(a00.value, a1.value)) - return new OperatorNode(node.op, node.fn, [a00a1, a0.args[1]], node.implicit) // constants on left - } } if (isCommutative(node, context)) { return new OperatorNode(node.op, node.fn, [a1, a0], node.implicit) // constants on left - } else { - return new OperatorNode(node.op, node.fn, [a0, a1], node.implicit) } } return new OperatorNode(node.op, node.fn, [a0, a1], node.implicit) - } else if (node.op === '/') { - if (isConstantNode(a0)) { - if (isZero(a0.value)) { - return node0 - } else if (isConstantNode(a1) && - (equal(a1.value, 1) || equal(a1.value, 2) || equal(a1.value, 4))) { - return new ConstantNode(divide(a0.value, a1.value)) - } + } + if (node.op === '/') { + if (isConstantNode(a0) && isZero(a0.value)) { + return node0 + } + if (isConstantNode(a1) && equal(a1.value, 1)) { + return a0 } return new OperatorNode(node.op, node.fn, [a0, a1]) - } else if (node.op === '^') { + } + if (node.op === '^') { if (isConstantNode(a1)) { if (isZero(a1.value)) { return node1 } else if (equal(a1.value, 1)) { return a0 + } + } + } + if (node.op === 'and') { + if (isConstantNode(a0)) { + if (a0.value) { + if (isAlwaysBoolean(a1)) return a1 } else { - if (isConstantNode(a0)) { - // fold constant - return new ConstantNode(pow(a0.value, a1.value)) - } else if (isOperatorNode(a0) && a0.isBinary() && a0.op === '^') { - const a01 = a0.args[1] - if (isConstantNode(a01)) { - return new OperatorNode(node.op, node.fn, [ - a0.args[0], - new ConstantNode(multiply(a01.value, a1.value)) - ]) - } - } + return nodeF + } + } + if (isConstantNode(a1)) { + if (a1.value) { + if (isAlwaysBoolean(a0)) return a0 + } else { + return nodeF + } + } + } + if (node.op === 'or') { + if (isConstantNode(a0)) { + if (a0.value) { + return nodeT + } else { + if (isAlwaysBoolean(a1)) return a1 + } + } + if (isConstantNode(a1)) { + if (a1.value) { + return nodeT + } else { + if (isAlwaysBoolean(a0)) return a0 } } } return new OperatorNode(node.op, node.fn, [a0, a1]) - } else if (isFunctionNode(node)) { - return new FunctionNode( - simplifyCore(node.fn), node.args.map(n => simplifyCore(n, options))) - } else if (isArrayNode(node)) { - return new ArrayNode( - node.items.map(n => simplifyCore(n, options))) - } else if (isAccessorNode(node)) { + } + if (isOperatorNode(node)) { + return new OperatorNode( + node.op, node.fn, node.args.map(a => _simplifyCore(a, options))) + } + if (isArrayNode(node)) { + return new ArrayNode(node.items.map(n => _simplifyCore(n, options))) + } + if (isAccessorNode(node)) { return new AccessorNode( - simplifyCore(node.object, options), simplifyCore(node.index, options)) - } else if (isIndexNode(node)) { + _simplifyCore(node.object, options), _simplifyCore(node.index, options)) + } + if (isIndexNode(node)) { return new IndexNode( - node.dimensions.map(n => simplifyCore(n, options))) - } else if (isObjectNode(node)) { + node.dimensions.map(n => _simplifyCore(n, options))) + } + if (isObjectNode(node)) { const newProps = {} for (const prop in node.properties) { - newProps[prop] = simplifyCore(node.properties[prop], options) + newProps[prop] = _simplifyCore(node.properties[prop], options) } return new ObjectNode(newProps) - } else { - // cannot simplify } + // cannot simplify return node } - return simplifyCore + return typed(name, { Node: _simplifyCore, 'Node,Object': _simplifyCore }) }) diff --git a/src/function/algebra/symbolicEqual.js b/src/function/algebra/symbolicEqual.js index 8bb4b24ca6..af24e6f7dd 100644 --- a/src/function/algebra/symbolicEqual.js +++ b/src/function/algebra/symbolicEqual.js @@ -54,32 +54,14 @@ export const createSymbolicEqual = /* #__PURE__ */ factory(name, dependencies, ( * Returns true if a valid manipulation making the expressions equal * is found. */ + function _symbolicEqual (e1, e2, options = {}) { + const diff = new OperatorNode('-', 'subtract', [e1, e2]) + const simplified = simplify(diff, {}, options) + return (isConstantNode(simplified) && !(simplified.value)) + } + return typed(name, { - 'string, string': function (s1, s2) { - return this(parse(s1), parse(s2), {}) - }, - 'string, string, Object': function (s1, s2, options) { - return this(parse(s1), parse(s2), options) - }, - 'Node, string': function (e1, s2) { - return this(e1, parse(s2), {}) - }, - 'Node, string, Object': function (e1, s2, options) { - return this(e1, parse(s2), options) - }, - 'string, Node': function (s1, e2) { - return this(parse(s1), e2, {}) - }, - 'string, Node, Object': function (s1, e2, options) { - return this(parse(s1), e2, options) - }, - 'Node, Node': function (e1, e2) { - return this(e1, e2, {}) - }, - 'Node, Node, Object': function (e1, e2, options) { - const diff = new OperatorNode('-', 'subtract', [e1, e2]) - const simplified = simplify(diff, {}, options) - return (isConstantNode(simplified) && !(simplified.value)) - } + 'Node, Node': _symbolicEqual, + 'Node, Node, Object': _symbolicEqual }) }) diff --git a/src/function/arithmetic/abs.js b/src/function/arithmetic/abs.js index e1c237e2e9..a300f315b8 100644 --- a/src/function/arithmetic/abs.js +++ b/src/function/arithmetic/abs.js @@ -33,25 +33,9 @@ export const createAbs = /* #__PURE__ */ factory(name, dependencies, ({ typed }) return typed(name, { number: absNumber, - Complex: function (x) { - return x.abs() - }, + 'Complex | BigNumber | Fraction | Unit': x => x.abs(), - BigNumber: function (x) { - return x.abs() - }, - - Fraction: function (x) { - return x.abs() - }, - - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since abs(0) = 0 - return deepMap(x, this, true) - }, - - Unit: function (x) { - return x.abs() - } + // deep map collection, skip zeros since abs(0) = 0 + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self, true)) }) }) diff --git a/src/function/arithmetic/add.js b/src/function/arithmetic/add.js index 68dfb27007..39ac8cd049 100644 --- a/src/function/arithmetic/add.js +++ b/src/function/arithmetic/add.js @@ -1,10 +1,8 @@ import { factory } from '../../utils/factory.js' -import { extend } from '../../utils/object.js' -import { createAlgorithm01 } from '../../type/matrix/utils/algorithm01.js' -import { createAlgorithm04 } from '../../type/matrix/utils/algorithm04.js' -import { createAlgorithm10 } from '../../type/matrix/utils/algorithm10.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo01xDSid } from '../../type/matrix/utils/matAlgo01xDSid.js' +import { createMatAlgo04xSidSid } from '../../type/matrix/utils/matAlgo04xSidSid.js' +import { createMatAlgo10xSids } from '../../type/matrix/utils/matAlgo10xSids.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'add' const dependencies = [ @@ -17,11 +15,10 @@ const dependencies = [ ] export const createAdd = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, addScalar, equalScalar, DenseMatrix, SparseMatrix }) => { - const algorithm01 = createAlgorithm01({ typed }) - const algorithm04 = createAlgorithm04({ typed, equalScalar }) - const algorithm10 = createAlgorithm10({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) + const matAlgo04xSidSid = createMatAlgo04xSidSid({ typed, equalScalar }) + const matAlgo10xSids = createMatAlgo10xSids({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Add two or more values, `x + y`. @@ -57,76 +54,26 @@ export const createAdd = /* #__PURE__ */ factory(name, dependencies, ({ typed, m * @param {number | BigNumber | Fraction | Complex | Unit | Array | Matrix} y Second value to add * @return {number | BigNumber | Fraction | Complex | Unit | Array | Matrix} Sum of `x` and `y` */ - return typed(name, extend({ - // we extend the signatures of addScalar with signatures dealing with matrices + return typed( + name, + { + 'any, any': addScalar, - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, addScalar) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm01(x, y, addScalar, false) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm01(y, x, addScalar, true) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm04(x, y, addScalar) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, + 'any, any, ...any': typed.referToSelf(self => (x, y, rest) => { + let result = self(x, y) - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, addScalar, false) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm10(x, y, addScalar, false) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, addScalar, true) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm10(y, x, addScalar, true) - }, + for (let i = 0; i < rest.length; i++) { + result = self(result, rest[i]) + } - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, addScalar, false).valueOf() + return result + }) }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, addScalar, true).valueOf() - }, - - 'any, any': addScalar, - - 'any, any, ...any': function (x, y, rest) { - let result = this(x, y) - - for (let i = 0; i < rest.length; i++) { - result = this(result, rest[i]) - } - - return result - } - }, addScalar.signatures)) + matrixAlgorithmSuite({ + elop: addScalar, + DS: matAlgo01xDSid, + SS: matAlgo04xSidSid, + Ss: matAlgo10xSids + }) + ) }) diff --git a/src/function/arithmetic/addScalar.js b/src/function/arithmetic/addScalar.js index 79ba8c0fa3..3a40ce079f 100644 --- a/src/function/arithmetic/addScalar.js +++ b/src/function/arithmetic/addScalar.js @@ -33,15 +33,20 @@ export const createAddScalar = /* #__PURE__ */ factory(name, dependencies, ({ ty return x.add(y) }, - 'Unit, Unit': function (x, y) { - if (x.value === null || x.value === undefined) throw new Error('Parameter x contains a unit with undefined value') - if (y.value === null || y.value === undefined) throw new Error('Parameter y contains a unit with undefined value') + 'Unit, Unit': typed.referToSelf(self => (x, y) => { + if (x.value === null || x.value === undefined) { + throw new Error('Parameter x contains a unit with undefined value') + } + if (y.value === null || y.value === undefined) { + throw new Error('Parameter y contains a unit with undefined value') + } if (!x.equalBase(y)) throw new Error('Units do not match') const res = x.clone() - res.value = this(res.value, y.value) + res.value = + typed.find(self, [res.valueType(), y.valueType()])(res.value, y.value) res.fixPrefix = false return res - } + }) }) }) diff --git a/src/function/arithmetic/cbrt.js b/src/function/arithmetic/cbrt.js index 1d90caa8cf..ae71eca054 100644 --- a/src/function/arithmetic/cbrt.js +++ b/src/function/arithmetic/cbrt.js @@ -1,6 +1,5 @@ import { factory } from '../../utils/factory.js' import { isBigNumber, isComplex, isFraction } from '../../utils/is.js' -import { deepMap } from '../../utils/collection.js' import { cbrtNumber } from '../../plain/number/index.js' const name = 'cbrt' @@ -19,7 +18,9 @@ export const createCbrt = /* #__PURE__ */ factory(name, dependencies, ({ config, /** * Calculate the cubic root of a value. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix cube root, this function does not + * apply to matrices. For a matrix, to take the cube root elementwise, + * see the examples. * * Syntax: * @@ -32,7 +33,7 @@ export const createCbrt = /* #__PURE__ */ factory(name, dependencies, ({ config, * math.cube(3) // returns 27 * math.cbrt(-64) // returns -4 * math.cbrt(math.unit('27 m^3')) // returns Unit 3 m - * math.cbrt([27, 64, 125]) // returns [3, 4, 5] + * math.map([27, 64, 125], x => math.cbrt(x)) // returns [3, 4, 5] * * const x = math.complex('8i') * math.cbrt(x) // returns Complex 1.7320508075689 + i @@ -46,13 +47,13 @@ export const createCbrt = /* #__PURE__ */ factory(name, dependencies, ({ config, * * square, sqrt, cube * - * @param {number | BigNumber | Complex | Unit | Array | Matrix} x + * @param {number | BigNumber | Complex | Unit} x * Value for which to calculate the cubic root. * @param {boolean} [allRoots] Optional, false by default. Only applicable * when `x` is a number or complex number. If true, all complex * roots are returned, if false (default) the principal root is * returned. - * @return {number | BigNumber | Complex | Unit | Array | Matrix} + * @return {number | BigNumber | Complex | Unit} * Returns the cubic root of `x` */ return typed(name, { @@ -68,12 +69,7 @@ export const createCbrt = /* #__PURE__ */ factory(name, dependencies, ({ config, return x.cbrt() }, - Unit: _cbrtUnit, - - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since cbrt(0) = 0 - return deepMap(x, this, true) - } + Unit: _cbrtUnit }) /** diff --git a/src/function/arithmetic/ceil.js b/src/function/arithmetic/ceil.js index bb5ab69994..dab20a45aa 100644 --- a/src/function/arithmetic/ceil.js +++ b/src/function/arithmetic/ceil.js @@ -3,9 +3,9 @@ import { factory } from '../../utils/factory.js' import { deepMap } from '../../utils/collection.js' import { nearlyEqual } from '../../utils/number.js' import { nearlyEqual as bigNearlyEqual } from '../../utils/bignumber/nearlyEqual.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatAlgo14xDs } from '../../type/matrix/utils/matAlgo14xDs.js' const name = 'ceil' const dependencies = ['typed', 'config', 'round', 'matrix', 'equalScalar', 'zeros', 'DenseMatrix'] @@ -36,9 +36,9 @@ export const createCeilNumber = /* #__PURE__ */ factory( ) export const createCeil = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, round, matrix, equalScalar, zeros, DenseMatrix }) => { - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matAlgo14xDs = createMatAlgo14xDs({ typed }) const ceilNumber = createCeilNumber({ typed, config, round }) /** @@ -122,35 +122,37 @@ export const createCeil = /* #__PURE__ */ factory(name, dependencies, ({ typed, return x.ceil(n.toNumber()) }, - 'Array | Matrix': function (x) { + 'Array | Matrix': typed.referToSelf(self => (x) => { // deep map collection, skip zeros since ceil(0) = 0 - return deepMap(x, this, true) - }, + return deepMap(x, self, true) + }), - 'Array, number | BigNumber': function (x, n) { + 'Array, number | BigNumber': typed.referToSelf(self => (x, n) => { // deep map collection, skip zeros since ceil(0) = 0 - return deepMap(x, i => this(i, n), true) - }, - - 'SparseMatrix, number | BigNumber': function (x, y) { - return algorithm11(x, y, this, false) - }, - - 'DenseMatrix, number | BigNumber': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'number | Complex | Fraction | BigNumber, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - }, - - 'number | Complex | Fraction | BigNumber, Matrix': function (x, y) { - if (equalScalar(x, 0)) return zeros(y.size(), y.storage()) - if (y.storage() === 'dense') { - return algorithm14(y, x, this, true) - } - return algorithm12(y, x, this, true) - } + return deepMap(x, i => self(i, n), true) + }), + + 'SparseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => { + return matAlgo11xS0s(x, y, self, false) + }), + + 'DenseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => { + return matAlgo14xDs(x, y, self, false) + }), + + 'number | Complex | Fraction | BigNumber, Array': + typed.referToSelf(self => (x, y) => { + // use matrix implementation + return matAlgo14xDs(matrix(y), x, self, true).valueOf() + }), + + 'number | Complex | Fraction | BigNumber, Matrix': + typed.referToSelf(self => (x, y) => { + if (equalScalar(x, 0)) return zeros(y.size(), y.storage()) + if (y.storage() === 'dense') { + return matAlgo14xDs(y, x, self, true) + } + return matAlgo12xSfs(y, x, self, true) + }) }) }) diff --git a/src/function/arithmetic/cube.js b/src/function/arithmetic/cube.js index 22814a78a9..87ddbe1c98 100644 --- a/src/function/arithmetic/cube.js +++ b/src/function/arithmetic/cube.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { cubeNumber } from '../../plain/number/index.js' const name = 'cube' @@ -8,7 +7,8 @@ const dependencies = ['typed'] export const createCube = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { /** * Compute the cube of a value, `x * x * x`. - * For matrices, the function is evaluated element wise. + * To avoid confusion with `pow(M,3)`, this function does not apply to matrices. + * If you wish to cube every entry of a matrix, see the examples. * * Syntax: * @@ -21,14 +21,14 @@ export const createCube = /* #__PURE__ */ factory(name, dependencies, ({ typed } * math.cube(4) // returns number 64 * 4 * 4 * 4 // returns number 64 * - * math.cube([1, 2, 3, 4]) // returns Array [1, 8, 27, 64] + * math.map([1, 2, 3, 4], math.cube) // returns Array [1, 8, 27, 64] * * See also: * * multiply, square, pow, cbrt * - * @param {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} x Number for which to calculate the cube - * @return {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} Cube of x + * @param {number | BigNumber | Fraction | Complex | Unit} x Number for which to calculate the cube + * @return {number | BigNumber | Fraction | Complex | Unit} Cube of x */ return typed(name, { number: cubeNumber, @@ -45,11 +45,6 @@ export const createCube = /* #__PURE__ */ factory(name, dependencies, ({ typed } return x.pow(3) // Is faster than mul()mul()mul() }, - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since cube(0) = 0 - return deepMap(x, this, true) - }, - Unit: function (x) { return x.pow(3) } diff --git a/src/function/arithmetic/divide.js b/src/function/arithmetic/divide.js index 214eaa3e39..9e5eea3bb9 100644 --- a/src/function/arithmetic/divide.js +++ b/src/function/arithmetic/divide.js @@ -1,7 +1,7 @@ import { factory } from '../../utils/factory.js' import { extend } from '../../utils/object.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo14xDs } from '../../type/matrix/utils/matAlgo14xDs.js' const name = 'divide' const dependencies = [ @@ -14,8 +14,8 @@ const dependencies = [ ] export const createDivide = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, multiply, equalScalar, divideScalar, inv }) => { - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matAlgo14xDs = createMatAlgo14xDs({ typed }) /** * Divide two values, `x / y`. @@ -60,16 +60,16 @@ export const createDivide = /* #__PURE__ */ factory(name, dependencies, ({ typed }, 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, divideScalar, false) + return matAlgo14xDs(x, y, divideScalar, false) }, 'SparseMatrix, any': function (x, y) { - return algorithm11(x, y, divideScalar, false) + return matAlgo11xS0s(x, y, divideScalar, false) }, 'Array, any': function (x, y) { // use matrix implementation - return algorithm14(matrix(x), y, divideScalar, false).valueOf() + return matAlgo14xDs(matrix(x), y, divideScalar, false).valueOf() }, 'any, Array | Matrix': function (x, y) { diff --git a/src/function/arithmetic/divideScalar.js b/src/function/arithmetic/divideScalar.js index cd50e319f4..e5d6194740 100644 --- a/src/function/arithmetic/divideScalar.js +++ b/src/function/arithmetic/divideScalar.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { typeOf } from '../../utils/is.js' const name = 'divideScalar' const dependencies = ['typed', 'numeric'] @@ -34,25 +33,10 @@ export const createDivideScalar = /* #__PURE__ */ factory(name, dependencies, ({ return x.div(y) }, - 'Unit, number | Fraction | BigNumber': function (x, y) { - const res = x.clone() - // TODO: move the divide function to Unit.js, it uses internals of Unit - const one = numeric(1, typeOf(y)) - res.value = this(((res.value === null) ? res._normalize(one) : res.value), y) - return res - }, - - 'number | Fraction | BigNumber, Unit': function (x, y) { - let res = y.clone() - res = res.pow(-1) - // TODO: move the divide function to Unit.js, it uses internals of Unit - const one = numeric(1, typeOf(x)) - res.value = this(x, ((y.value === null) ? y._normalize(one) : y.value)) - return res - }, + 'Unit, number | Complex | Fraction | BigNumber | Unit': + (x, y) => x.divide(y), - 'Unit, Unit': function (x, y) { - return x.divide(y) - } + 'number | Fraction | Complex | BigNumber, Unit': + (x, y) => y.divideInto(x) }) }) diff --git a/src/function/arithmetic/dotDivide.js b/src/function/arithmetic/dotDivide.js index 12e380d986..35711177a1 100644 --- a/src/function/arithmetic/dotDivide.js +++ b/src/function/arithmetic/dotDivide.js @@ -1,11 +1,10 @@ import { factory } from '../../utils/factory.js' -import { createAlgorithm02 } from '../../type/matrix/utils/algorithm02.js' -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm07 } from '../../type/matrix/utils/algorithm07.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo02xDS0 } from '../../type/matrix/utils/matAlgo02xDS0.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo07xSSf } from '../../type/matrix/utils/matAlgo07xSSf.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'dotDivide' const dependencies = [ @@ -17,13 +16,12 @@ const dependencies = [ ] export const createDotDivide = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, divideScalar, DenseMatrix }) => { - const algorithm02 = createAlgorithm02({ typed, equalScalar }) - const algorithm03 = createAlgorithm03({ typed }) - const algorithm07 = createAlgorithm07({ typed, DenseMatrix }) - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo07xSSf = createMatAlgo07xSSf({ typed, DenseMatrix }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Divide two matrices element wise. The function accepts both matrices and @@ -51,65 +49,12 @@ export const createDotDivide = /* #__PURE__ */ factory(name, dependencies, ({ ty * @param {number | BigNumber | Fraction | Complex | Unit | Array | Matrix} y Denominator * @return {number | BigNumber | Fraction | Complex | Unit | Array | Matrix} Quotient, `x ./ y` */ - return typed(name, { - - 'any, any': divideScalar, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm07(x, y, divideScalar, false) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm02(y, x, divideScalar, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, divideScalar, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, divideScalar) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm11(x, y, divideScalar, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, divideScalar, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm12(y, x, divideScalar, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, divideScalar, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, divideScalar, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, divideScalar, true).valueOf() - } - }) + return typed(name, matrixAlgorithmSuite({ + elop: divideScalar, + SS: matAlgo07xSSf, + DS: matAlgo03xDSf, + SD: matAlgo02xDS0, + Ss: matAlgo11xS0s, + sS: matAlgo12xSfs + })) }) diff --git a/src/function/arithmetic/dotMultiply.js b/src/function/arithmetic/dotMultiply.js index 6a43d61500..5552be8460 100644 --- a/src/function/arithmetic/dotMultiply.js +++ b/src/function/arithmetic/dotMultiply.js @@ -1,9 +1,8 @@ import { factory } from '../../utils/factory.js' -import { createAlgorithm02 } from '../../type/matrix/utils/algorithm02.js' -import { createAlgorithm09 } from '../../type/matrix/utils/algorithm09.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo02xDS0 } from '../../type/matrix/utils/matAlgo02xDS0.js' +import { createMatAlgo09xS0Sf } from '../../type/matrix/utils/matAlgo09xS0Sf.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'dotMultiply' const dependencies = [ @@ -14,11 +13,10 @@ const dependencies = [ ] export const createDotMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, multiplyScalar }) => { - const algorithm02 = createAlgorithm02({ typed, equalScalar }) - const algorithm09 = createAlgorithm09({ typed, equalScalar }) - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) + const matAlgo09xS0Sf = createMatAlgo09xS0Sf({ typed, equalScalar }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Multiply two matrices element wise. The function accepts both matrices and @@ -46,65 +44,10 @@ export const createDotMultiply = /* #__PURE__ */ factory(name, dependencies, ({ * @param {number | BigNumber | Fraction | Complex | Unit | Array | Matrix} y Right hand value * @return {number | BigNumber | Fraction | Complex | Unit | Array | Matrix} Multiplication of `x` and `y` */ - return typed(name, { - - 'any, any': multiplyScalar, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm09(x, y, multiplyScalar, false) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm02(y, x, multiplyScalar, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm02(x, y, multiplyScalar, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, multiplyScalar) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm11(x, y, multiplyScalar, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, multiplyScalar, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm11(y, x, multiplyScalar, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, multiplyScalar, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, multiplyScalar, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, multiplyScalar, true).valueOf() - } - }) + return typed(name, matrixAlgorithmSuite({ + elop: multiplyScalar, + SS: matAlgo09xS0Sf, + DS: matAlgo02xDS0, + Ss: matAlgo11xS0s + })) }) diff --git a/src/function/arithmetic/dotPow.js b/src/function/arithmetic/dotPow.js index 74f2793136..83c5045fd5 100644 --- a/src/function/arithmetic/dotPow.js +++ b/src/function/arithmetic/dotPow.js @@ -1,10 +1,9 @@ import { factory } from '../../utils/factory.js' -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm07 } from '../../type/matrix/utils/algorithm07.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo07xSSf } from '../../type/matrix/utils/matAlgo07xSSf.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'dotPow' const dependencies = [ @@ -16,12 +15,21 @@ const dependencies = [ ] export const createDotPow = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar, matrix, pow, DenseMatrix }) => { - const algorithm03 = createAlgorithm03({ typed }) - const algorithm07 = createAlgorithm07({ typed, DenseMatrix }) - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo07xSSf = createMatAlgo07xSSf({ typed, DenseMatrix }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) + + const powScalarSignatures = {} + for (const signature in pow.signatures) { + if (Object.prototype.hasOwnProperty.call(pow.signatures, signature)) { + if (!signature.includes('Matrix') && !signature.includes('Array')) { + powScalarSignatures[signature] = pow.signatures[signature] + } + } + } + const powScalar = typed(powScalarSignatures) /** * Calculates the power of x to y element wise. @@ -46,65 +54,11 @@ export const createDotPow = /* #__PURE__ */ factory(name, dependencies, ({ typed * @param {number | BigNumber | Complex | Unit | Array | Matrix} y The exponent * @return {number | BigNumber | Complex | Unit | Array | Matrix} The value of `x` to the power `y` */ - return typed(name, { - - 'any, any': pow, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm07(x, y, pow, false) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm03(y, x, pow, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, pow, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, pow) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm11(x, y, this, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm12(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + return typed(name, matrixAlgorithmSuite({ + elop: powScalar, + SS: matAlgo07xSSf, + DS: matAlgo03xDSf, + Ss: matAlgo11xS0s, + sS: matAlgo12xSfs + })) }) diff --git a/src/function/arithmetic/exp.js b/src/function/arithmetic/exp.js index 606c885f1e..4f9daccff0 100644 --- a/src/function/arithmetic/exp.js +++ b/src/function/arithmetic/exp.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { expNumber } from '../../plain/number/index.js' const name = 'exp' @@ -7,8 +6,10 @@ const dependencies = ['typed'] export const createExp = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { /** - * Calculate the exponent of a value. - * For matrices, the function is evaluated element wise. + * Calculate the exponential of a value. + * For matrices, if you want the matrix exponential of square matrix, use + * the `expm` function; if you want to take the exponential of each element, + * see the examples. * * Syntax: * @@ -20,7 +21,7 @@ export const createExp = /* #__PURE__ */ factory(name, dependencies, ({ typed }) * math.pow(math.e, 2) // returns number 7.3890560989306495 * math.log(math.exp(2)) // returns number 2 * - * math.exp([1, 2, 3]) + * math.map([1, 2, 3], math.exp) * // returns Array [ * // 2.718281828459045, * // 7.3890560989306495, @@ -29,10 +30,10 @@ export const createExp = /* #__PURE__ */ factory(name, dependencies, ({ typed }) * * See also: * - * expm1, log, pow + * expm1, expm, log, pow * - * @param {number | BigNumber | Complex | Array | Matrix} x A number or matrix to exponentiate - * @return {number | BigNumber | Complex | Array | Matrix} Exponent of `x` + * @param {number | BigNumber | Complex} x A number to exponentiate + * @return {number | BigNumber | Complex} Exponential of `x` */ return typed(name, { number: expNumber, @@ -43,11 +44,6 @@ export const createExp = /* #__PURE__ */ factory(name, dependencies, ({ typed }) BigNumber: function (x) { return x.exp() - }, - - 'Array | Matrix': function (x) { - // TODO: exp(sparse) should return a dense matrix since exp(0)==1 - return deepMap(x, this) } }) }) diff --git a/src/function/arithmetic/expm1.js b/src/function/arithmetic/expm1.js index 37845015f7..93e1556f31 100644 --- a/src/function/arithmetic/expm1.js +++ b/src/function/arithmetic/expm1.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { expm1Number } from '../../plain/number/index.js' const name = 'expm1' @@ -8,7 +7,10 @@ const dependencies = ['typed', 'Complex'] export const createExpm1 = /* #__PURE__ */ factory(name, dependencies, ({ typed, Complex }) => { /** * Calculate the value of subtracting 1 from the exponential value. - * For matrices, the function is evaluated element wise. + * This function is more accurate than `math.exp(x)-1` when `x` is near 0 + * To avoid ambiguity with the matrix exponential `expm`, this function + * does not operate on matrices; if you wish to apply it elementwise, see + * the examples. * * Syntax: * @@ -18,9 +20,11 @@ export const createExpm1 = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * math.expm1(2) // returns number 6.38905609893065 * math.pow(math.e, 2) - 1 // returns number 6.3890560989306495 + * math.expm1(1e-8) // returns number 1.0000000050000001e-8 + * math.exp(1e-8) - 1 // returns number 9.9999999392253e-9 * math.log(math.expm1(2) + 1) // returns number 2 * - * math.expm1([1, 2, 3]) + * math.map([1, 2, 3], math.expm1) * // returns Array [ * // 1.718281828459045, * // 6.3890560989306495, @@ -29,10 +33,10 @@ export const createExpm1 = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * See also: * - * exp, log, pow + * exp, expm, log, pow * - * @param {number | BigNumber | Complex | Array | Matrix} x A number or matrix to apply expm1 - * @return {number | BigNumber | Complex | Array | Matrix} Exponent of `x` + * @param {number | BigNumber | Complex} x A number or matrix to apply expm1 + * @return {number | BigNumber | Complex} Exponential of `x`, minus one */ return typed(name, { number: expm1Number, @@ -47,10 +51,6 @@ export const createExpm1 = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber: function (x) { return x.exp().minus(1) - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) } }) }) diff --git a/src/function/arithmetic/fix.js b/src/function/arithmetic/fix.js index 6ac672c324..104bf3cdfa 100644 --- a/src/function/arithmetic/fix.js +++ b/src/function/arithmetic/fix.js @@ -1,7 +1,7 @@ import { factory } from '../../utils/factory.js' import { deepMap } from '../../utils/collection.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatAlgo14xDs } from '../../type/matrix/utils/matAlgo14xDs.js' const name = 'fix' const dependencies = ['typed', 'Complex', 'matrix', 'ceil', 'floor', 'equalScalar', 'zeros', 'DenseMatrix'] @@ -21,8 +21,8 @@ export const createFixNumber = /* #__PURE__ */ factory( ) export const createFix = /* #__PURE__ */ factory(name, dependencies, ({ typed, Complex, matrix, ceil, floor, equalScalar, zeros, DenseMatrix }) => { - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matAlgo14xDs = createMatAlgo14xDs({ typed }) const fixNumber = createFixNumber({ typed, ceil, floor }) /** @@ -103,27 +103,29 @@ export const createFix = /* #__PURE__ */ factory(name, dependencies, ({ typed, C return x.s < 0 ? ceil(x, n) : floor(x, n) }, - 'Array | Matrix': function (x) { + 'Array | Matrix': typed.referToSelf(self => (x) => { // deep map collection, skip zeros since fix(0) = 0 - return deepMap(x, this, true) - }, + return deepMap(x, self, true) + }), - 'Array | Matrix, number | BigNumber': function (x, n) { + 'Array | Matrix, number | BigNumber': typed.referToSelf(self => (x, n) => { // deep map collection, skip zeros since fix(0) = 0 - return deepMap(x, i => this(i, n), true) - }, - - 'number | Complex | Fraction | BigNumber, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - }, - - 'number | Complex | Fraction | BigNumber, Matrix': function (x, y) { - if (equalScalar(x, 0)) return zeros(y.size(), y.storage()) - if (y.storage() === 'dense') { - return algorithm14(y, x, this, true) - } - return algorithm12(y, x, this, true) - } + return deepMap(x, i => self(i, n), true) + }), + + 'number | Complex | Fraction | BigNumber, Array': + typed.referToSelf(self => (x, y) => { + // use matrix implementation + return matAlgo14xDs(matrix(y), x, self, true).valueOf() + }), + + 'number | Complex | Fraction | BigNumber, Matrix': + typed.referToSelf(self => (x, y) => { + if (equalScalar(x, 0)) return zeros(y.size(), y.storage()) + if (y.storage() === 'dense') { + return matAlgo14xDs(y, x, self, true) + } + return matAlgo12xSfs(y, x, self, true) + }) }) }) diff --git a/src/function/arithmetic/floor.js b/src/function/arithmetic/floor.js index fb04c78355..b6e405a68b 100644 --- a/src/function/arithmetic/floor.js +++ b/src/function/arithmetic/floor.js @@ -3,9 +3,9 @@ import { factory } from '../../utils/factory.js' import { deepMap } from '../../utils/collection.js' import { nearlyEqual } from '../../utils/number.js' import { nearlyEqual as bigNearlyEqual } from '../../utils/bignumber/nearlyEqual.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatAlgo14xDs } from '../../type/matrix/utils/matAlgo14xDs.js' const name = 'floor' const dependencies = ['typed', 'config', 'round', 'matrix', 'equalScalar', 'zeros', 'DenseMatrix'] @@ -36,9 +36,9 @@ export const createFloorNumber = /* #__PURE__ */ factory( ) export const createFloor = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, round, matrix, equalScalar, zeros, DenseMatrix }) => { - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matAlgo14xDs = createMatAlgo14xDs({ typed }) const floorNumber = createFloorNumber({ typed, config, round }) /** @@ -125,35 +125,37 @@ export const createFloor = /* #__PURE__ */ factory(name, dependencies, ({ typed, return x.floor(n.toNumber()) }, - 'Array | Matrix': function (x) { + 'Array | Matrix': typed.referToSelf(self => (x) => { // deep map collection, skip zeros since floor(0) = 0 - return deepMap(x, this, true) - }, + return deepMap(x, self, true) + }), - 'Array, number | BigNumber': function (x, n) { + 'Array, number | BigNumber': typed.referToSelf(self => (x, n) => { // deep map collection, skip zeros since ceil(0) = 0 - return deepMap(x, i => this(i, n), true) - }, - - 'SparseMatrix, number | BigNumber': function (x, y) { - return algorithm11(x, y, this, false) - }, - - 'DenseMatrix, number | BigNumber': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'number | Complex | Fraction | BigNumber, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - }, - - 'number | Complex | Fraction | BigNumber, Matrix': function (x, y) { - if (equalScalar(x, 0)) return zeros(y.size(), y.storage()) - if (y.storage() === 'dense') { - return algorithm14(y, x, this, true) - } - return algorithm12(y, x, this, true) - } + return deepMap(x, i => self(i, n), true) + }), + + 'SparseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => { + return matAlgo11xS0s(x, y, self, false) + }), + + 'DenseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => { + return matAlgo14xDs(x, y, self, false) + }), + + 'number | Complex | Fraction | BigNumber, Array': + typed.referToSelf(self => (x, y) => { + // use matrix implementation + return matAlgo14xDs(matrix(y), x, self, true).valueOf() + }), + + 'number | Complex | Fraction | BigNumber, Matrix': + typed.referToSelf(self => (x, y) => { + if (equalScalar(x, 0)) return zeros(y.size(), y.storage()) + if (y.storage() === 'dense') { + return matAlgo14xDs(y, x, self, true) + } + return matAlgo12xSfs(y, x, self, true) + }) }) }) diff --git a/src/function/arithmetic/gcd.js b/src/function/arithmetic/gcd.js index 440565008a..fc94f23c43 100644 --- a/src/function/arithmetic/gcd.js +++ b/src/function/arithmetic/gcd.js @@ -1,9 +1,8 @@ import { factory } from '../../utils/factory.js' -import { createAlgorithm01 } from '../../type/matrix/utils/algorithm01.js' -import { createAlgorithm04 } from '../../type/matrix/utils/algorithm04.js' -import { createAlgorithm10 } from '../../type/matrix/utils/algorithm10.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo01xDSid } from '../../type/matrix/utils/matAlgo01xDSid.js' +import { createMatAlgo04xSidSid } from '../../type/matrix/utils/matAlgo04xSidSid.js' +import { createMatAlgo10xSids } from '../../type/matrix/utils/matAlgo10xSids.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' import { gcdNumber } from '../../plain/number/index.js' const name = 'gcd' @@ -16,11 +15,21 @@ const dependencies = [ ] export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, BigNumber, DenseMatrix }) => { - const algorithm01 = createAlgorithm01({ typed }) - const algorithm04 = createAlgorithm04({ typed, equalScalar }) - const algorithm10 = createAlgorithm10({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) + const matAlgo04xSidSid = createMatAlgo04xSidSid({ typed, equalScalar }) + const matAlgo10xSids = createMatAlgo10xSids({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) + + const gcdTypes = 'number | BigNumber | Fraction | Matrix | Array' + const gcdManySignature = {} + gcdManySignature[`${gcdTypes}, ${gcdTypes}, ...${gcdTypes}`] = + typed.referToSelf(self => (a, b, args) => { + let res = self(a, b) + for (let i = 0; i < args.length; i++) { + res = self(res, args[i]) + } + return res + }) /** * Calculate the greatest common divisor for two or more values or arrays. @@ -47,82 +56,20 @@ export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, m * @param {... number | BigNumber | Fraction | Array | Matrix} args Two or more integer numbers * @return {number | BigNumber | Fraction | Array | Matrix} The greatest common divisor */ - return typed(name, { - - 'number, number': gcdNumber, - - 'BigNumber, BigNumber': _gcdBigNumber, - - 'Fraction, Fraction': function (x, y) { - return x.gcd(y) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm04(x, y, this) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm01(y, x, this, true) + return typed( + name, + { + 'number, number': gcdNumber, + 'BigNumber, BigNumber': _gcdBigNumber, + 'Fraction, Fraction': (x, y) => x.gcd(y) }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm01(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, number | BigNumber': function (x, y) { - return algorithm10(x, y, this, false) - }, - - 'DenseMatrix, number | BigNumber': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'number | BigNumber, SparseMatrix': function (x, y) { - return algorithm10(y, x, this, true) - }, - - 'number | BigNumber, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, number | BigNumber': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'number | BigNumber, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - }, - - // TODO: need a smarter notation here - 'Array | Matrix | number | BigNumber, Array | Matrix | number | BigNumber, ...Array | Matrix | number | BigNumber': function (a, b, args) { - let res = this(a, b) - for (let i = 0; i < args.length; i++) { - res = this(res, args[i]) - } - return res - } - }) + matrixAlgorithmSuite({ + SS: matAlgo04xSidSid, + DS: matAlgo01xDSid, + Ss: matAlgo10xSids + }), + gcdManySignature + ) /** * Calculate gcd for BigNumbers diff --git a/src/function/arithmetic/hypot.js b/src/function/arithmetic/hypot.js index 9b4dd06784..4b5d3e160a 100644 --- a/src/function/arithmetic/hypot.js +++ b/src/function/arithmetic/hypot.js @@ -1,5 +1,6 @@ import { factory } from '../../utils/factory.js' import { flatten } from '../../utils/array.js' +import { isComplex } from '../../utils/is.js' const name = 'hypot' const dependencies = [ @@ -45,13 +46,9 @@ export const createHypot = /* #__PURE__ */ factory(name, dependencies, ({ typed, return typed(name, { '... number | BigNumber': _hypot, - Array: function (x) { - return this.apply(this, flatten(x)) - }, + Array: _hypot, - Matrix: function (x) { - return this.apply(this, flatten(x.toArray())) - } + Matrix: M => _hypot(flatten(M.toArray())) }) /** @@ -67,6 +64,9 @@ export const createHypot = /* #__PURE__ */ factory(name, dependencies, ({ typed, let largest = 0 for (let i = 0; i < args.length; i++) { + if (isComplex(args[i])) { + throw new TypeError('Unexpected type of argument to hypot') + } const value = abs(args[i]) if (smaller(largest, value)) { result = multiplyScalar(result, diff --git a/src/function/arithmetic/lcm.js b/src/function/arithmetic/lcm.js index 997fe98502..8f16f0d54a 100644 --- a/src/function/arithmetic/lcm.js +++ b/src/function/arithmetic/lcm.js @@ -1,9 +1,8 @@ import { factory } from '../../utils/factory.js' -import { createAlgorithm02 } from '../../type/matrix/utils/algorithm02.js' -import { createAlgorithm06 } from '../../type/matrix/utils/algorithm06.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo02xDS0 } from '../../type/matrix/utils/matAlgo02xDS0.js' +import { createMatAlgo06xS0S0 } from '../../type/matrix/utils/matAlgo06xS0S0.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' import { lcmNumber } from '../../plain/number/index.js' const name = 'lcm' @@ -14,11 +13,21 @@ const dependencies = [ ] export const createLcm = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar }) => { - const algorithm02 = createAlgorithm02({ typed, equalScalar }) - const algorithm06 = createAlgorithm06({ typed, equalScalar }) - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) + const matAlgo06xS0S0 = createMatAlgo06xS0S0({ typed, equalScalar }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) + + const lcmTypes = 'number | BigNumber | Fraction | Matrix | Array' + const lcmManySignature = {} + lcmManySignature[`${lcmTypes}, ${lcmTypes}, ...${lcmTypes}`] = + typed.referToSelf(self => (a, b, args) => { + let res = self(a, b) + for (let i = 0; i < args.length; i++) { + res = self(res, args[i]) + } + return res + }) /** * Calculate the least common multiple for two or more values or arrays. @@ -49,81 +58,19 @@ export const createLcm = /* #__PURE__ */ factory(name, dependencies, ({ typed, m * @param {... number | BigNumber | Array | Matrix} args Two or more integer numbers * @return {number | BigNumber | Array | Matrix} The least common multiple */ - return typed(name, { - 'number, number': lcmNumber, - - 'BigNumber, BigNumber': _lcmBigNumber, - - 'Fraction, Fraction': function (x, y) { - return x.lcm(y) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm06(x, y, this) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm02(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm02(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, number | BigNumber': function (x, y) { - return algorithm11(x, y, this, false) - }, - - 'DenseMatrix, number | BigNumber': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'number | BigNumber, SparseMatrix': function (x, y) { - return algorithm11(y, x, this, true) - }, - - 'number | BigNumber, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) + return typed( + name, { + 'number, number': lcmNumber, + 'BigNumber, BigNumber': _lcmBigNumber, + 'Fraction, Fraction': (x, y) => x.lcm(y) }, - - 'Array, number | BigNumber': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'number | BigNumber, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - }, - - // TODO: need a smarter notation here - 'Array | Matrix | number | BigNumber, Array | Matrix | number | BigNumber, ...Array | Matrix | number | BigNumber': function (a, b, args) { - let res = this(a, b) - for (let i = 0; i < args.length; i++) { - res = this(res, args[i]) - } - return res - } - }) + matrixAlgorithmSuite({ + SS: matAlgo06xS0S0, + DS: matAlgo02xDS0, + Ss: matAlgo11xS0s + }), + lcmManySignature + ) /** * Calculate lcm for two BigNumbers diff --git a/src/function/arithmetic/log.js b/src/function/arithmetic/log.js index ad7b54ea36..b41ab76c5f 100644 --- a/src/function/arithmetic/log.js +++ b/src/function/arithmetic/log.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { logNumber } from '../../plain/number/index.js' const name = 'log' @@ -9,7 +8,8 @@ export const createLog = /* #__PURE__ */ factory(name, dependencies, ({ typed, c /** * Calculate the logarithm of a value. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix logarithm, this function does not + * apply to matrices. * * Syntax: * @@ -32,12 +32,12 @@ export const createLog = /* #__PURE__ */ factory(name, dependencies, ({ typed, c * * exp, log2, log10, log1p * - * @param {number | BigNumber | Complex | Array | Matrix} x + * @param {number | BigNumber | Complex} x * Value for which to calculate the logarithm. * @param {number | BigNumber | Complex} [base=e] * Optional base for the logarithm. If not provided, the natural * logarithm of `x` is calculated. - * @return {number | BigNumber | Complex | Array | Matrix} + * @return {number | BigNumber | Complex} * Returns the logarithm of `x` */ return typed(name, { @@ -63,13 +63,9 @@ export const createLog = /* #__PURE__ */ factory(name, dependencies, ({ typed, c } }, - 'Array | Matrix': function (x) { - return deepMap(x, this) - }, - - 'any, any': function (x, base) { + 'any, any': typed.referToSelf(self => (x, base) => { // calculate logarithm for a specified base, log(x, base) - return divideScalar(this(x), this(base)) - } + return divideScalar(self(x), self(base)) + }) }) }) diff --git a/src/function/arithmetic/log10.js b/src/function/arithmetic/log10.js index 998b2a97ce..d96c6044a9 100644 --- a/src/function/arithmetic/log10.js +++ b/src/function/arithmetic/log10.js @@ -54,8 +54,6 @@ export const createLog10 = /* #__PURE__ */ factory(name, dependencies, ({ typed, } }, - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/function/arithmetic/log1p.js b/src/function/arithmetic/log1p.js index c036cdafe1..a210750d56 100644 --- a/src/function/arithmetic/log1p.js +++ b/src/function/arithmetic/log1p.js @@ -59,14 +59,12 @@ export const createLog1p = /* #__PURE__ */ factory(name, dependencies, ({ typed, } }, - 'Array | Matrix': function (x) { - return deepMap(x, this) - }, + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)), - 'any, any': function (x, base) { + 'any, any': typed.referToSelf(self => (x, base) => { // calculate logarithm for a specified base, log1p(x, base) - return divideScalar(this(x), log(base)) - } + return divideScalar(self(x), log(base)) + }) }) /** diff --git a/src/function/arithmetic/log2.js b/src/function/arithmetic/log2.js index de3b96c331..cefd0ba2a0 100644 --- a/src/function/arithmetic/log2.js +++ b/src/function/arithmetic/log2.js @@ -52,9 +52,7 @@ export const createLog2 = /* #__PURE__ */ factory(name, dependencies, ({ typed, } }, - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) /** diff --git a/src/function/arithmetic/mod.js b/src/function/arithmetic/mod.js index 7ecb7e015c..b104f6c55d 100644 --- a/src/function/arithmetic/mod.js +++ b/src/function/arithmetic/mod.js @@ -1,12 +1,11 @@ import { factory } from '../../utils/factory.js' -import { createAlgorithm02 } from '../../type/matrix/utils/algorithm02.js' -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm05 } from '../../type/matrix/utils/algorithm05.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo02xDS0 } from '../../type/matrix/utils/matAlgo02xDS0.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo05xSfSf } from '../../type/matrix/utils/matAlgo05xSfSf.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' import { modNumber } from '../../plain/number/index.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'mod' const dependencies = [ @@ -17,13 +16,12 @@ const dependencies = [ ] export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, DenseMatrix }) => { - const algorithm02 = createAlgorithm02({ typed, equalScalar }) - const algorithm03 = createAlgorithm03({ typed }) - const algorithm05 = createAlgorithm05({ typed, equalScalar }) - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo05xSfSf = createMatAlgo05xSfSf({ typed, equalScalar }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Calculates the modulus, the remainder of an integer division. @@ -60,80 +58,32 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, m * @param {number | BigNumber | Fraction | Array | Matrix} y Divisor * @return {number | BigNumber | Fraction | Array | Matrix} Returns the remainder of `x` divided by `y`. */ - return typed(name, { - - 'number, number': modNumber, - - 'BigNumber, BigNumber': function (x, y) { - if (y.isNeg()) { - throw new Error('Cannot calculate mod for a negative divisor') - } - return y.isZero() ? x : x.mod(y) - }, - - 'Fraction, Fraction': function (x, y) { - if (y.compare(0) < 0) { - throw new Error('Cannot calculate mod for a negative divisor') + return typed( + name, + { + 'number, number': modNumber, + + 'BigNumber, BigNumber': function (x, y) { + if (y.isNeg()) { + throw new Error('Cannot calculate mod for a negative divisor') + } + return y.isZero() ? x : x.mod(y) + }, + + 'Fraction, Fraction': function (x, y) { + if (y.compare(0) < 0) { + throw new Error('Cannot calculate mod for a negative divisor') + } + // Workaround suggested in Fraction.js library to calculate correct modulo for negative dividend + return x.compare(0) >= 0 ? x.mod(y) : x.mod(y).add(y).mod(y) } - // Workaround suggested in Fraction.js library to calculate correct modulo for negative dividend - return x.compare(0) >= 0 ? x.mod(y) : x.mod(y).add(y).mod(y) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm05(x, y, this, false) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm02(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm11(x, y, this, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm12(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + matrixAlgorithmSuite({ + SS: matAlgo05xSfSf, + DS: matAlgo03xDSf, + SD: matAlgo02xDS0, + Ss: matAlgo11xS0s, + sS: matAlgo12xSfs + }) + ) }) diff --git a/src/function/arithmetic/multiply.js b/src/function/arithmetic/multiply.js index 739cd4b591..daf14bc46d 100644 --- a/src/function/arithmetic/multiply.js +++ b/src/function/arithmetic/multiply.js @@ -1,9 +1,8 @@ import { factory } from '../../utils/factory.js' import { isMatrix } from '../../utils/is.js' -import { extend } from '../../utils/object.js' import { arraySize } from '../../utils/array.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo14xDs } from '../../type/matrix/utils/matAlgo14xDs.js' const name = 'multiply' const dependencies = [ @@ -16,8 +15,8 @@ const dependencies = [ ] export const createMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, addScalar, multiplyScalar, equalScalar, dot }) => { - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matAlgo14xDs = createMatAlgo14xDs({ typed }) function _validateMatrixDimensions (size1, size2) { // check left operand dimensions @@ -794,18 +793,18 @@ export const createMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typ * @param {number | BigNumber | Fraction | Complex | Unit | Array | Matrix} y Second value to multiply * @return {number | BigNumber | Fraction | Complex | Unit | Array | Matrix} Multiplication of `x` and `y` */ - return typed(name, extend({ + return typed(name, multiplyScalar, { // we extend the signatures of multiplyScalar with signatures dealing with matrices - 'Array, Array': function (x, y) { + 'Array, Array': typed.referTo('Matrix, Matrix', selfMM => (x, y) => { // check dimensions _validateMatrixDimensions(arraySize(x), arraySize(y)) // use dense matrix implementation - const m = this(matrix(x), matrix(y)) + const m = selfMM(matrix(x), matrix(y)) // return array or scalar return isMatrix(m) ? m.valueOf() : m - }, + }), 'Matrix, Matrix': function (x, y) { // dimensions @@ -834,52 +833,50 @@ export const createMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typ return _multiplyMatrixMatrix(x, y) }, - 'Matrix, Array': function (x, y) { - // use Matrix * Matrix implementation - return this(x, matrix(y)) - }, + 'Matrix, Array': typed.referTo('Matrix,Matrix', selfMM => + (x, y) => selfMM(x, matrix(y))), - 'Array, Matrix': function (x, y) { + 'Array, Matrix': typed.referToSelf(self => (x, y) => { // use Matrix * Matrix implementation - return this(matrix(x, y.storage()), y) - }, + return self(matrix(x, y.storage()), y) + }), 'SparseMatrix, any': function (x, y) { - return algorithm11(x, y, multiplyScalar, false) + return matAlgo11xS0s(x, y, multiplyScalar, false) }, 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, multiplyScalar, false) + return matAlgo14xDs(x, y, multiplyScalar, false) }, 'any, SparseMatrix': function (x, y) { - return algorithm11(y, x, multiplyScalar, true) + return matAlgo11xS0s(y, x, multiplyScalar, true) }, 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, multiplyScalar, true) + return matAlgo14xDs(y, x, multiplyScalar, true) }, 'Array, any': function (x, y) { // use matrix implementation - return algorithm14(matrix(x), y, multiplyScalar, false).valueOf() + return matAlgo14xDs(matrix(x), y, multiplyScalar, false).valueOf() }, 'any, Array': function (x, y) { // use matrix implementation - return algorithm14(matrix(y), x, multiplyScalar, true).valueOf() + return matAlgo14xDs(matrix(y), x, multiplyScalar, true).valueOf() }, 'any, any': multiplyScalar, - 'any, any, ...any': function (x, y, rest) { - let result = this(x, y) + 'any, any, ...any': typed.referToSelf(self => (x, y, rest) => { + let result = self(x, y) for (let i = 0; i < rest.length; i++) { - result = this(result, rest[i]) + result = self(result, rest[i]) } return result - } - }, multiplyScalar.signatures)) + }) + }) }) diff --git a/src/function/arithmetic/multiplyScalar.js b/src/function/arithmetic/multiplyScalar.js index e0734f2f28..61555555d0 100644 --- a/src/function/arithmetic/multiplyScalar.js +++ b/src/function/arithmetic/multiplyScalar.js @@ -33,21 +33,8 @@ export const createMultiplyScalar = /* #__PURE__ */ factory(name, dependencies, return x.mul(y) }, - 'number | Fraction | BigNumber | Complex, Unit': function (x, y) { - const res = y.clone() - res.value = (res.value === null) ? res._normalize(x) : this(res.value, x) - return res - }, - - 'Unit, number | Fraction | BigNumber | Complex': function (x, y) { - const res = x.clone() - res.value = (res.value === null) ? res._normalize(y) : this(res.value, y) - return res - }, - - 'Unit, Unit': function (x, y) { - return x.multiply(y) - } + 'number | Fraction | BigNumber | Complex, Unit': (x, y) => y.multiply(x), + 'Unit, number | Fraction | BigNumber | Complex | Unit': (x, y) => x.multiply(y) }) }) diff --git a/src/function/arithmetic/norm.js b/src/function/arithmetic/norm.js index 849a4b4eab..3c8cf4890e 100644 --- a/src/function/arithmetic/norm.js +++ b/src/function/arithmetic/norm.js @@ -98,13 +98,6 @@ export const createNorm = /* #__PURE__ */ factory( return _norm(x, 2) }, - 'number | Complex | BigNumber | boolean, number | BigNumber | string': function ( - x - ) { - // ignore second parameter, TODO: remove the option of second parameter for these types - return this(x) - }, - 'Array, number | BigNumber | string': function (x, p) { return _norm(matrix(x), p) }, diff --git a/src/function/arithmetic/nthRoot.js b/src/function/arithmetic/nthRoot.js index 12fcc0ebb4..b02f67edb4 100644 --- a/src/function/arithmetic/nthRoot.js +++ b/src/function/arithmetic/nthRoot.js @@ -1,10 +1,9 @@ import { factory } from '../../utils/factory.js' -import { createAlgorithm01 } from '../../type/matrix/utils/algorithm01.js' -import { createAlgorithm02 } from '../../type/matrix/utils/algorithm02.js' -import { createAlgorithm06 } from '../../type/matrix/utils/algorithm06.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo01xDSid } from '../../type/matrix/utils/matAlgo01xDSid.js' +import { createMatAlgo02xDS0 } from '../../type/matrix/utils/matAlgo02xDS0.js' +import { createMatAlgo06xS0S0 } from '../../type/matrix/utils/matAlgo06xS0S0.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' import { nthRootNumber } from '../../plain/number/index.js' const name = 'nthRoot' @@ -16,12 +15,11 @@ const dependencies = [ ] export const createNthRoot = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, BigNumber }) => { - const algorithm01 = createAlgorithm01({ typed }) - const algorithm02 = createAlgorithm02({ typed, equalScalar }) - const algorithm06 = createAlgorithm06({ typed, equalScalar }) - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) + const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) + const matAlgo06xS0S0 = createMatAlgo06xS0S0({ typed, equalScalar }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Calculate the nth root of a value. @@ -52,108 +50,73 @@ export const createNthRoot = /* #__PURE__ */ factory(name, dependencies, ({ type * @param {number | BigNumber} [root=2] The root. * @return {number | Complex | Array | Matrix} Returns the nth root of `a` */ - const complexErr = ('' + - 'Complex number not supported in function nthRoot. ' + - 'Use nthRoots instead.' - ) - return typed(name, { - - number: nthRootNumber, - 'number, number': nthRootNumber, - - BigNumber: function (x) { - return _bigNthRoot(x, new BigNumber(2)) - }, - Complex: function (x) { - throw new Error(complexErr) - }, - 'Complex, number': function (x, y) { - throw new Error(complexErr) - }, - 'BigNumber, BigNumber': _bigNthRoot, - - 'Array | Matrix': function (x) { - return this(x, 2) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - // density must be one (no zeros in matrix) - if (y.density() === 1) { - // sparse + sparse - return algorithm06(x, y, this) - } else { - // throw exception - throw new Error('Root must be non-zero') - } - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm02(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - // density must be one (no zeros in matrix) - if (y.density() === 1) { - // dense + sparse - return algorithm01(x, y, this, false) - } else { - // throw exception - throw new Error('Root must be non-zero') - } - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, number | BigNumber': function (x, y) { - return algorithm11(x, y, this, false) - }, - - 'DenseMatrix, number | BigNumber': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'number | BigNumber, SparseMatrix': function (x, y) { - // density must be one (no zeros in matrix) - if (y.density() === 1) { - // sparse - scalar - return algorithm11(y, x, this, true) - } else { - // throw exception - throw new Error('Root must be non-zero') - } - }, - - 'number | BigNumber, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, number | BigNumber': function (x, y) { - // use matrix implementation - return this(matrix(x), y).valueOf() - }, + function complexErr () { + throw new Error( + 'Complex number not supported in function nthRoot. Use nthRoots instead.') + } - 'number | BigNumber, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)).valueOf() - } - }) + return typed( + name, + { + number: nthRootNumber, + 'number, number': nthRootNumber, + + BigNumber: x => _bigNthRoot(x, new BigNumber(2)), + 'BigNumber, BigNumber': _bigNthRoot, + + Complex: complexErr, + 'Complex, number': complexErr, + + Array: typed.referTo('DenseMatrix,number', selfDn => + x => selfDn(matrix(x), 2).valueOf()), + DenseMatrix: typed.referTo('DenseMatrix,number', selfDn => + x => selfDn(x, 2)), + SparseMatrix: typed.referTo('SparseMatrix,number', selfSn => + x => selfSn(x, 2)), + + 'SparseMatrix, SparseMatrix': typed.referToSelf(self => (x, y) => { + // density must be one (no zeros in matrix) + if (y.density() === 1) { + // sparse + sparse + return matAlgo06xS0S0(x, y, self) + } else { + // throw exception + throw new Error('Root must be non-zero') + } + }), + + 'DenseMatrix, SparseMatrix': typed.referToSelf(self => (x, y) => { + // density must be one (no zeros in matrix) + if (y.density() === 1) { + // dense + sparse + return matAlgo01xDSid(x, y, self, false) + } else { + // throw exception + throw new Error('Root must be non-zero') + } + }), + + 'Array, SparseMatrix': typed.referTo('DenseMatrix,SparseMatrix', selfDS => + (x, y) => selfDS(matrix(x), y)), + + 'number | BigNumber, SparseMatrix': typed.referToSelf(self => (x, y) => { + // density must be one (no zeros in matrix) + if (y.density() === 1) { + // sparse - scalar + return matAlgo11xS0s(y, x, self, true) + } else { + // throw exception + throw new Error('Root must be non-zero') + } + }) + }, + matrixAlgorithmSuite({ + scalar: 'number | BigNumber', + SD: matAlgo02xDS0, + Ss: matAlgo11xS0s, + sS: false + }) + ) /** * Calculate the nth root of a for BigNumbers, solve x^root == a diff --git a/src/function/arithmetic/round.js b/src/function/arithmetic/round.js index 015065eaca..3391eb87fb 100644 --- a/src/function/arithmetic/round.js +++ b/src/function/arithmetic/round.js @@ -1,8 +1,8 @@ import { factory } from '../../utils/factory.js' import { deepMap } from '../../utils/collection.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatAlgo14xDs } from '../../type/matrix/utils/matAlgo14xDs.js' import { roundNumber } from '../../plain/number/index.js' const NO_INT = 'Number of decimals in function round must be an integer' @@ -18,9 +18,9 @@ const dependencies = [ ] export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, zeros, BigNumber, DenseMatrix }) => { - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matAlgo14xDs = createMatAlgo14xDs({ typed }) /** * Round a value towards the nearest integer. @@ -109,45 +109,43 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, return x.round(n.toNumber()) }, - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since round(0) = 0 - return deepMap(x, this, true) - }, + // deep map collection, skip zeros since round(0) = 0 + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self, true)), - 'SparseMatrix, number | BigNumber': function (x, y) { - return algorithm11(x, y, this, false) - }, + 'SparseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => { + return matAlgo11xS0s(x, y, self, false) + }), - 'DenseMatrix, number | BigNumber': function (x, y) { - return algorithm14(x, y, this, false) - }, + 'DenseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => { + return matAlgo14xDs(x, y, self, false) + }), - 'Array, number | BigNumber': function (x, y) { + 'Array, number | BigNumber': typed.referToSelf(self => (x, y) => { // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, + return matAlgo14xDs(matrix(x), y, self, false).valueOf() + }), - 'number | Complex | BigNumber | Fraction, SparseMatrix': function (x, y) { + 'number | Complex | BigNumber | Fraction, SparseMatrix': typed.referToSelf(self => (x, y) => { // check scalar is zero if (equalScalar(x, 0)) { // do not execute algorithm, result will be a zero matrix return zeros(y.size(), y.storage()) } - return algorithm12(y, x, this, true) - }, + return matAlgo12xSfs(y, x, self, true) + }), - 'number | Complex | BigNumber | Fraction, DenseMatrix': function (x, y) { + 'number | Complex | BigNumber | Fraction, DenseMatrix': typed.referToSelf(self => (x, y) => { // check scalar is zero if (equalScalar(x, 0)) { // do not execute algorithm, result will be a zero matrix return zeros(y.size(), y.storage()) } - return algorithm14(y, x, this, true) - }, + return matAlgo14xDs(y, x, self, true) + }), - 'number | Complex | BigNumber | Fraction, Array': function (x, y) { + 'number | Complex | BigNumber | Fraction, Array': typed.referToSelf(self => (x, y) => { // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } + return matAlgo14xDs(matrix(y), x, self, true).valueOf() + }) }) }) diff --git a/src/function/arithmetic/sign.js b/src/function/arithmetic/sign.js index 9eea1f8cff..b8f4545758 100644 --- a/src/function/arithmetic/sign.js +++ b/src/function/arithmetic/sign.js @@ -51,16 +51,14 @@ export const createSign = /* #__PURE__ */ factory(name, dependencies, ({ typed, return new Fraction(x.s, 1) }, - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since sign(0) = 0 - return deepMap(x, this, true) - }, + // deep map collection, skip zeros since sign(0) = 0 + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self, true)), - Unit: function (x) { + Unit: typed.referToSelf(self => x => { if (!x._isDerived() && x.units[0].unit.offset !== 0) { throw new TypeError('sign is ambiguous for units with offset') } - return this(x.value) - } + return typed.find(self, x.valueType())(x.value) + }) }) }) diff --git a/src/function/arithmetic/sqrt.js b/src/function/arithmetic/sqrt.js index 6d3b9a34ec..8174dd72e3 100644 --- a/src/function/arithmetic/sqrt.js +++ b/src/function/arithmetic/sqrt.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' const name = 'sqrt' const dependencies = ['config', 'typed', 'Complex'] @@ -8,7 +7,9 @@ export const createSqrt = /* #__PURE__ */ factory(name, dependencies, ({ config, /** * Calculate the square root of a value. * - * For matrices, the function is evaluated element wise. + * For matrices, if you want the matrix square root of a square matrix, + * use the `sqrtm` function. If you wish to apply `sqrt` elementwise to + * a matrix M, use `math.map(M, math.sqrt)`. * * Syntax: * @@ -24,9 +25,9 @@ export const createSqrt = /* #__PURE__ */ factory(name, dependencies, ({ config, * * square, multiply, cube, cbrt, sqrtm * - * @param {number | BigNumber | Complex | Array | Matrix | Unit} x + * @param {number | BigNumber | Complex | Unit} x * Value for which to calculate the square root. - * @return {number | BigNumber | Complex | Array | Matrix | Unit} + * @return {number | BigNumber | Complex | Unit} * Returns the square root of `x` */ return typed('sqrt', { @@ -45,11 +46,6 @@ export const createSqrt = /* #__PURE__ */ factory(name, dependencies, ({ config, } }, - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since sqrt(0) = 0 - return deepMap(x, this, true) - }, - Unit: function (x) { // Someday will work for complex units when they are implemented return x.pow(0.5) diff --git a/src/function/arithmetic/square.js b/src/function/arithmetic/square.js index c9e9adde8e..5162f7fac6 100644 --- a/src/function/arithmetic/square.js +++ b/src/function/arithmetic/square.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { squareNumber } from '../../plain/number/index.js' const name = 'square' @@ -8,7 +7,9 @@ const dependencies = ['typed'] export const createSquare = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { /** * Compute the square of a value, `x * x`. - * For matrices, the function is evaluated element wise. + * To avoid confusion with multiplying a square matrix by itself, + * this function does not apply to matrices. If you wish to square + * every element of a matrix, see the examples. * * Syntax: * @@ -21,15 +22,15 @@ export const createSquare = /* #__PURE__ */ factory(name, dependencies, ({ typed * math.pow(3, 2) // returns number 9 * math.multiply(3, 3) // returns number 9 * - * math.square([1, 2, 3, 4]) // returns Array [1, 4, 9, 16] + * math.map([1, 2, 3, 4], math.square) // returns Array [1, 4, 9, 16] * * See also: * * multiply, cube, sqrt, pow * - * @param {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} x + * @param {number | BigNumber | Fraction | Complex | Unit} x * Number for which to calculate the square - * @return {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} + * @return {number | BigNumber | Fraction | Complex | Unit} * Squared value */ return typed(name, { @@ -47,11 +48,6 @@ export const createSquare = /* #__PURE__ */ factory(name, dependencies, ({ typed return x.mul(x) }, - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since square(0) = 0 - return deepMap(x, this, true) - }, - Unit: function (x) { return x.pow(2) } diff --git a/src/function/arithmetic/subtract.js b/src/function/arithmetic/subtract.js index abbb2f9204..9221b6f74e 100644 --- a/src/function/arithmetic/subtract.js +++ b/src/function/arithmetic/subtract.js @@ -1,11 +1,10 @@ import { factory } from '../../utils/factory.js' -import { DimensionError } from '../../error/DimensionError.js' -import { createAlgorithm01 } from '../../type/matrix/utils/algorithm01.js' -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm05 } from '../../type/matrix/utils/algorithm05.js' -import { createAlgorithm10 } from '../../type/matrix/utils/algorithm10.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo01xDSid } from '../../type/matrix/utils/matAlgo01xDSid.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo05xSfSf } from '../../type/matrix/utils/matAlgo05xSfSf.js' +import { createMatAlgo10xSids } from '../../type/matrix/utils/matAlgo10xSids.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'subtract' const dependencies = [ @@ -20,12 +19,12 @@ const dependencies = [ export const createSubtract = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, addScalar, unaryMinus, DenseMatrix }) => { // TODO: split function subtract in two: subtract and subtractScalar - const algorithm01 = createAlgorithm01({ typed }) - const algorithm03 = createAlgorithm03({ typed }) - const algorithm05 = createAlgorithm05({ typed, equalScalar }) - const algorithm10 = createAlgorithm10({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo05xSfSf = createMatAlgo05xSfSf({ typed, equalScalar }) + const matAlgo10xSids = createMatAlgo10xSids({ typed, DenseMatrix }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Subtract two values, `x - y`. @@ -60,118 +59,41 @@ export const createSubtract = /* #__PURE__ */ factory(name, dependencies, ({ typ * @return {number | BigNumber | Fraction | Complex | Unit | Array | Matrix} * Subtraction of `x` and `y` */ - return typed(name, { - - 'number, number': function (x, y) { - return x - y - }, - - 'Complex, Complex': function (x, y) { - return x.sub(y) - }, - - 'BigNumber, BigNumber': function (x, y) { - return x.minus(y) - }, - - 'Fraction, Fraction': function (x, y) { - return x.sub(y) - }, - - 'Unit, Unit': function (x, y) { - if (x.value === null) { - throw new Error('Parameter x contains a unit with undefined value') - } - - if (y.value === null) { - throw new Error('Parameter y contains a unit with undefined value') - } - - if (!x.equalBase(y)) { - throw new Error('Units do not match') - } - - const res = x.clone() - res.value = this(res.value, y.value) - res.fixPrefix = false - - return res - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - checkEqualDimensions(x, y) - return algorithm05(x, y, this) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - checkEqualDimensions(x, y) - return algorithm03(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - checkEqualDimensions(x, y) - return algorithm01(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - checkEqualDimensions(x, y) - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm10(x, unaryMinus(y), addScalar) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, this) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm10(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + return typed( + name, + { + 'number, number': (x, y) => x - y, + 'Complex, Complex': (x, y) => x.sub(y), + 'BigNumber, BigNumber': (x, y) => x.minus(y), + 'Fraction, Fraction': (x, y) => x.sub(y), + + 'Unit, Unit': typed.referToSelf(self => (x, y) => { + if (x.value === null) { + throw new Error('Parameter x contains a unit with undefined value') + } + + if (y.value === null) { + throw new Error('Parameter y contains a unit with undefined value') + } + + if (!x.equalBase(y)) { + throw new Error('Units do not match') + } + + const res = x.clone() + res.value = + typed.find(self, [res.valueType(), y.valueType()])(res.value, y.value) + res.fixPrefix = false + + return res + }) + }, + matrixAlgorithmSuite({ + SS: matAlgo05xSfSf, + DS: matAlgo01xDSid, + SD: matAlgo03xDSf, + Ss: matAlgo12xSfs, + sS: matAlgo10xSids + }) + ) }) - -/** - * Check whether matrix x and y have the same number of dimensions. - * Throws a DimensionError when dimensions are not equal - * @param {Matrix} x - * @param {Matrix} y - */ -function checkEqualDimensions (x, y) { - const xsize = x.size() - const ysize = y.size() - - if (xsize.length !== ysize.length) { - throw new DimensionError(xsize.length, ysize.length) - } -} diff --git a/src/function/arithmetic/unaryMinus.js b/src/function/arithmetic/unaryMinus.js index 73f6a7ea88..32da37c240 100644 --- a/src/function/arithmetic/unaryMinus.js +++ b/src/function/arithmetic/unaryMinus.js @@ -32,28 +32,16 @@ export const createUnaryMinus = /* #__PURE__ */ factory(name, dependencies, ({ t return typed(name, { number: unaryMinusNumber, - Complex: function (x) { - return x.neg() - }, + 'Complex | BigNumber | Fraction': x => x.neg(), - BigNumber: function (x) { - return x.neg() - }, - - Fraction: function (x) { - return x.neg() - }, - - Unit: function (x) { + Unit: typed.referToSelf(self => x => { const res = x.clone() - res.value = this(x.value) + res.value = typed.find(self, res.valueType())(x.value) return res - }, + }), - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since unaryMinus(0) = 0 - return deepMap(x, this, true) - } + // deep map collection, skip zeros since unaryMinus(0) = 0 + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self, true)) // TODO: add support for string }) diff --git a/src/function/arithmetic/unaryPlus.js b/src/function/arithmetic/unaryPlus.js index bcaccd1941..805eda1fe3 100644 --- a/src/function/arithmetic/unaryPlus.js +++ b/src/function/arithmetic/unaryPlus.js @@ -49,10 +49,8 @@ export const createUnaryPlus = /* #__PURE__ */ factory(name, dependencies, ({ ty return x.clone() }, - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since unaryPlus(0) = 0 - return deepMap(x, this, true) - }, + // deep map collection, skip zeros since unaryPlus(0) = 0 + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self, true)), 'boolean | string': function (x) { // convert to a number or bignumber diff --git a/src/function/bitwise/bitAnd.js b/src/function/bitwise/bitAnd.js index aaaf8524fd..268c4e1424 100644 --- a/src/function/bitwise/bitAnd.js +++ b/src/function/bitwise/bitAnd.js @@ -1,10 +1,9 @@ import { bitAndBigNumber } from '../../utils/bignumber/bitwise.js' -import { createAlgorithm02 } from '../../type/matrix/utils/algorithm02.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm06 } from '../../type/matrix/utils/algorithm06.js' +import { createMatAlgo02xDS0 } from '../../type/matrix/utils/matAlgo02xDS0.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo06xS0S0 } from '../../type/matrix/utils/matAlgo06xS0S0.js' import { factory } from '../../utils/factory.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' import { bitAndNumber } from '../../plain/number/index.js' const name = 'bitAnd' @@ -15,11 +14,10 @@ const dependencies = [ ] export const createBitAnd = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar }) => { - const algorithm02 = createAlgorithm02({ typed, equalScalar }) - const algorithm06 = createAlgorithm06({ typed, equalScalar }) - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) + const matAlgo06xS0S0 = createMatAlgo06xS0S0({ typed, equalScalar }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Bitwise AND two values, `x & y`. @@ -43,67 +41,16 @@ export const createBitAnd = /* #__PURE__ */ factory(name, dependencies, ({ typed * @param {number | BigNumber | Array | Matrix} y Second value to and * @return {number | BigNumber | Array | Matrix} AND of `x` and `y` */ - return typed(name, { - - 'number, number': bitAndNumber, - - 'BigNumber, BigNumber': bitAndBigNumber, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm06(x, y, this, false) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm02(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm02(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm11(x, y, this, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm11(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + return typed( + name, + { + 'number, number': bitAndNumber, + 'BigNumber, BigNumber': bitAndBigNumber + }, + matrixAlgorithmSuite({ + SS: matAlgo06xS0S0, + DS: matAlgo02xDS0, + Ss: matAlgo11xS0s + }) + ) }) diff --git a/src/function/bitwise/bitNot.js b/src/function/bitwise/bitNot.js index db532974a4..cc649800b0 100644 --- a/src/function/bitwise/bitNot.js +++ b/src/function/bitwise/bitNot.js @@ -31,11 +31,7 @@ export const createBitNot = /* #__PURE__ */ factory(name, dependencies, ({ typed */ return typed(name, { number: bitNotNumber, - BigNumber: bitNotBigNumber, - - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/function/bitwise/bitOr.js b/src/function/bitwise/bitOr.js index 908450ce8f..aa9e1c6e39 100644 --- a/src/function/bitwise/bitOr.js +++ b/src/function/bitwise/bitOr.js @@ -1,10 +1,9 @@ import { bitOrBigNumber } from '../../utils/bignumber/bitwise.js' import { factory } from '../../utils/factory.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm10 } from '../../type/matrix/utils/algorithm10.js' -import { createAlgorithm04 } from '../../type/matrix/utils/algorithm04.js' -import { createAlgorithm01 } from '../../type/matrix/utils/algorithm01.js' +import { createMatAlgo10xSids } from '../../type/matrix/utils/matAlgo10xSids.js' +import { createMatAlgo04xSidSid } from '../../type/matrix/utils/matAlgo04xSidSid.js' +import { createMatAlgo01xDSid } from '../../type/matrix/utils/matAlgo01xDSid.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' import { bitOrNumber } from '../../plain/number/index.js' const name = 'bitOr' @@ -16,11 +15,10 @@ const dependencies = [ ] export const createBitOr = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, DenseMatrix }) => { - const algorithm01 = createAlgorithm01({ typed }) - const algorithm04 = createAlgorithm04({ typed, equalScalar }) - const algorithm10 = createAlgorithm10({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) + const matAlgo04xSidSid = createMatAlgo04xSidSid({ typed, equalScalar }) + const matAlgo10xSids = createMatAlgo10xSids({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Bitwise OR two values, `x | y`. @@ -45,67 +43,16 @@ export const createBitOr = /* #__PURE__ */ factory(name, dependencies, ({ typed, * @param {number | BigNumber | Array | Matrix} y Second value to or * @return {number | BigNumber | Array | Matrix} OR of `x` and `y` */ - return typed(name, { - - 'number, number': bitOrNumber, - - 'BigNumber, BigNumber': bitOrBigNumber, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm04(x, y, this) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm01(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm01(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm10(x, y, this, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm10(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + return typed( + name, + { + 'number, number': bitOrNumber, + 'BigNumber, BigNumber': bitOrBigNumber + }, + matrixAlgorithmSuite({ + SS: matAlgo04xSidSid, + DS: matAlgo01xDSid, + Ss: matAlgo10xSids + }) + ) }) diff --git a/src/function/bitwise/bitXor.js b/src/function/bitwise/bitXor.js index b2edac3139..8d5bedf21e 100644 --- a/src/function/bitwise/bitXor.js +++ b/src/function/bitwise/bitXor.js @@ -1,10 +1,9 @@ import { bitXor as bigBitXor } from '../../utils/bignumber/bitwise.js' -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm07 } from '../../type/matrix/utils/algorithm07.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo07xSSf } from '../../type/matrix/utils/matAlgo07xSSf.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' import { factory } from '../../utils/factory.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' import { bitXorNumber } from '../../plain/number/index.js' const name = 'bitXor' @@ -15,11 +14,10 @@ const dependencies = [ ] export const createBitXor = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, DenseMatrix }) => { - const algorithm03 = createAlgorithm03({ typed }) - const algorithm07 = createAlgorithm07({ typed, DenseMatrix }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo07xSSf = createMatAlgo07xSSf({ typed, DenseMatrix }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Bitwise XOR two values, `x ^ y`. @@ -43,67 +41,16 @@ export const createBitXor = /* #__PURE__ */ factory(name, dependencies, ({ typed * @param {number | BigNumber | Array | Matrix} y Second value to xor * @return {number | BigNumber | Array | Matrix} XOR of `x` and `y` */ - return typed(name, { - - 'number, number': bitXorNumber, - - 'BigNumber, BigNumber': bigBitXor, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm07(x, y, this) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm03(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm12(x, y, this, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm12(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + return typed( + name, + { + 'number, number': bitXorNumber, + 'BigNumber, BigNumber': bigBitXor + }, + matrixAlgorithmSuite({ + SS: matAlgo07xSSf, + DS: matAlgo03xDSf, + Ss: matAlgo12xSfs + }) + ) }) diff --git a/src/function/bitwise/leftShift.js b/src/function/bitwise/leftShift.js index 0e72039bf4..bbc77a11da 100644 --- a/src/function/bitwise/leftShift.js +++ b/src/function/bitwise/leftShift.js @@ -1,11 +1,12 @@ -import { createAlgorithm02 } from '../../type/matrix/utils/algorithm02.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm01 } from '../../type/matrix/utils/algorithm01.js' -import { createAlgorithm10 } from '../../type/matrix/utils/algorithm10.js' -import { createAlgorithm08 } from '../../type/matrix/utils/algorithm08.js' +import { createMatAlgo02xDS0 } from '../../type/matrix/utils/matAlgo02xDS0.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo14xDs } from '../../type/matrix/utils/matAlgo14xDs.js' +import { createMatAlgo01xDSid } from '../../type/matrix/utils/matAlgo01xDSid.js' +import { createMatAlgo10xSids } from '../../type/matrix/utils/matAlgo10xSids.js' +import { createMatAlgo08xS0Sid } from '../../type/matrix/utils/matAlgo08xS0Sid.js' import { factory } from '../../utils/factory.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' +import { createUseMatrixForArrayScalar } from './useMatrixForArrayScalar.js' import { leftShiftNumber } from '../../plain/number/index.js' import { leftShiftBigNumber } from '../../utils/bignumber/bitwise.js' @@ -19,13 +20,14 @@ const dependencies = [ ] export const createLeftShift = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, zeros, DenseMatrix }) => { - const algorithm01 = createAlgorithm01({ typed }) - const algorithm02 = createAlgorithm02({ typed, equalScalar }) - const algorithm08 = createAlgorithm08({ typed, equalScalar }) - const algorithm10 = createAlgorithm10({ typed, DenseMatrix }) - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) + const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) + const matAlgo08xS0Sid = createMatAlgo08xS0Sid({ typed, equalScalar }) + const matAlgo10xSids = createMatAlgo10xSids({ typed, DenseMatrix }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matAlgo14xDs = createMatAlgo14xDs({ typed }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) + const useMatrixForArrayScalar = createUseMatrixForArrayScalar({ typed, matrix }) /** * Bitwise left logical shift of a value x by y number of bits, `x << y`. @@ -50,83 +52,50 @@ export const createLeftShift = /* #__PURE__ */ factory(name, dependencies, ({ ty * @param {number | BigNumber} y Amount of shifts * @return {number | BigNumber | Array | Matrix} `x` shifted left `y` times */ - return typed(name, { - - 'number, number': leftShiftNumber, - - 'BigNumber, BigNumber': leftShiftBigNumber, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm08(x, y, this, false) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm02(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm01(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) + return typed( + name, + { + 'number, number': leftShiftNumber, + + 'BigNumber, BigNumber': leftShiftBigNumber, + + 'SparseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => { + // check scalar + if (equalScalar(y, 0)) { + return x.clone() + } + return matAlgo11xS0s(x, y, self, false) + }), + + 'DenseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => { + // check scalar + if (equalScalar(y, 0)) { + return x.clone() + } + return matAlgo14xDs(x, y, self, false) + }), + + 'number | BigNumber, SparseMatrix': typed.referToSelf(self => (x, y) => { + // check scalar + if (equalScalar(x, 0)) { + return zeros(y.size(), y.storage()) + } + return matAlgo10xSids(y, x, self, true) + }), + + 'number | BigNumber, DenseMatrix': typed.referToSelf(self => (x, y) => { + // check scalar + if (equalScalar(x, 0)) { + return zeros(y.size(), y.storage()) + } + return matAlgo14xDs(y, x, self, true) + }) }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, number | BigNumber': function (x, y) { - // check scalar - if (equalScalar(y, 0)) { - return x.clone() - } - return algorithm11(x, y, this, false) - }, - - 'DenseMatrix, number | BigNumber': function (x, y) { - // check scalar - if (equalScalar(y, 0)) { - return x.clone() - } - return algorithm14(x, y, this, false) - }, - - 'number | BigNumber, SparseMatrix': function (x, y) { - // check scalar - if (equalScalar(x, 0)) { - return zeros(y.size(), y.storage()) - } - return algorithm10(y, x, this, true) - }, - - 'number | BigNumber, DenseMatrix': function (x, y) { - // check scalar - if (equalScalar(x, 0)) { - return zeros(y.size(), y.storage()) - } - return algorithm14(y, x, this, true) - }, - - 'Array, number | BigNumber': function (x, y) { - // use matrix implementation - return this(matrix(x), y).valueOf() - }, - - 'number | BigNumber, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)).valueOf() - } - }) + useMatrixForArrayScalar, + matrixAlgorithmSuite({ + SS: matAlgo08xS0Sid, + DS: matAlgo01xDSid, + SD: matAlgo02xDS0 + }) + ) }) diff --git a/src/function/bitwise/rightArithShift.js b/src/function/bitwise/rightArithShift.js index 16c806f44a..0e7d294e6a 100644 --- a/src/function/bitwise/rightArithShift.js +++ b/src/function/bitwise/rightArithShift.js @@ -1,12 +1,13 @@ import { rightArithShiftBigNumber } from '../../utils/bignumber/bitwise.js' -import { createAlgorithm02 } from '../../type/matrix/utils/algorithm02.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm01 } from '../../type/matrix/utils/algorithm01.js' -import { createAlgorithm10 } from '../../type/matrix/utils/algorithm10.js' -import { createAlgorithm08 } from '../../type/matrix/utils/algorithm08.js' +import { createMatAlgo02xDS0 } from '../../type/matrix/utils/matAlgo02xDS0.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo14xDs } from '../../type/matrix/utils/matAlgo14xDs.js' +import { createMatAlgo01xDSid } from '../../type/matrix/utils/matAlgo01xDSid.js' +import { createMatAlgo10xSids } from '../../type/matrix/utils/matAlgo10xSids.js' +import { createMatAlgo08xS0Sid } from '../../type/matrix/utils/matAlgo08xS0Sid.js' import { factory } from '../../utils/factory.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' +import { createUseMatrixForArrayScalar } from './useMatrixForArrayScalar.js' import { rightArithShiftNumber } from '../../plain/number/index.js' const name = 'rightArithShift' @@ -19,13 +20,14 @@ const dependencies = [ ] export const createRightArithShift = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, zeros, DenseMatrix }) => { - const algorithm01 = createAlgorithm01({ typed }) - const algorithm02 = createAlgorithm02({ typed, equalScalar }) - const algorithm08 = createAlgorithm08({ typed, equalScalar }) - const algorithm10 = createAlgorithm10({ typed, DenseMatrix }) - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) + const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) + const matAlgo08xS0Sid = createMatAlgo08xS0Sid({ typed, equalScalar }) + const matAlgo10xSids = createMatAlgo10xSids({ typed, DenseMatrix }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matAlgo14xDs = createMatAlgo14xDs({ typed }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) + const useMatrixForArrayScalar = createUseMatrixForArrayScalar({ typed, matrix }) /** * Bitwise right arithmetic shift of a value x by y number of bits, `x >> y`. @@ -50,83 +52,50 @@ export const createRightArithShift = /* #__PURE__ */ factory(name, dependencies, * @param {number | BigNumber} y Amount of shifts * @return {number | BigNumber | Array | Matrix} `x` sign-filled shifted right `y` times */ - return typed(name, { - - 'number, number': rightArithShiftNumber, - - 'BigNumber, BigNumber': rightArithShiftBigNumber, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm08(x, y, this, false) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm02(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm01(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) + return typed( + name, + { + 'number, number': rightArithShiftNumber, + + 'BigNumber, BigNumber': rightArithShiftBigNumber, + + 'SparseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => { + // check scalar + if (equalScalar(y, 0)) { + return x.clone() + } + return matAlgo11xS0s(x, y, self, false) + }), + + 'DenseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => { + // check scalar + if (equalScalar(y, 0)) { + return x.clone() + } + return matAlgo14xDs(x, y, self, false) + }), + + 'number | BigNumber, SparseMatrix': typed.referToSelf(self => (x, y) => { + // check scalar + if (equalScalar(x, 0)) { + return zeros(y.size(), y.storage()) + } + return matAlgo10xSids(y, x, self, true) + }), + + 'number | BigNumber, DenseMatrix': typed.referToSelf(self => (x, y) => { + // check scalar + if (equalScalar(x, 0)) { + return zeros(y.size(), y.storage()) + } + return matAlgo14xDs(y, x, self, true) + }) }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, number | BigNumber': function (x, y) { - // check scalar - if (equalScalar(y, 0)) { - return x.clone() - } - return algorithm11(x, y, this, false) - }, - - 'DenseMatrix, number | BigNumber': function (x, y) { - // check scalar - if (equalScalar(y, 0)) { - return x.clone() - } - return algorithm14(x, y, this, false) - }, - - 'number | BigNumber, SparseMatrix': function (x, y) { - // check scalar - if (equalScalar(x, 0)) { - return zeros(y.size(), y.storage()) - } - return algorithm10(y, x, this, true) - }, - - 'number | BigNumber, DenseMatrix': function (x, y) { - // check scalar - if (equalScalar(x, 0)) { - return zeros(y.size(), y.storage()) - } - return algorithm14(y, x, this, true) - }, - - 'Array, number | BigNumber': function (x, y) { - // use matrix implementation - return this(matrix(x), y).valueOf() - }, - - 'number | BigNumber, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)).valueOf() - } - }) + useMatrixForArrayScalar, + matrixAlgorithmSuite({ + SS: matAlgo08xS0Sid, + DS: matAlgo01xDSid, + SD: matAlgo02xDS0 + }) + ) }) diff --git a/src/function/bitwise/rightLogShift.js b/src/function/bitwise/rightLogShift.js index 7354f1bab8..a543bace93 100644 --- a/src/function/bitwise/rightLogShift.js +++ b/src/function/bitwise/rightLogShift.js @@ -1,12 +1,13 @@ -import { createAlgorithm02 } from '../../type/matrix/utils/algorithm02.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm01 } from '../../type/matrix/utils/algorithm01.js' -import { createAlgorithm10 } from '../../type/matrix/utils/algorithm10.js' -import { createAlgorithm08 } from '../../type/matrix/utils/algorithm08.js' +import { createMatAlgo02xDS0 } from '../../type/matrix/utils/matAlgo02xDS0.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo14xDs } from '../../type/matrix/utils/matAlgo14xDs.js' +import { createMatAlgo01xDSid } from '../../type/matrix/utils/matAlgo01xDSid.js' +import { createMatAlgo10xSids } from '../../type/matrix/utils/matAlgo10xSids.js' +import { createMatAlgo08xS0Sid } from '../../type/matrix/utils/matAlgo08xS0Sid.js' import { factory } from '../../utils/factory.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' import { rightLogShiftNumber } from '../../plain/number/index.js' +import { createUseMatrixForArrayScalar } from './useMatrixForArrayScalar.js' const name = 'rightLogShift' const dependencies = [ @@ -18,13 +19,14 @@ const dependencies = [ ] export const createRightLogShift = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, zeros, DenseMatrix }) => { - const algorithm01 = createAlgorithm01({ typed }) - const algorithm02 = createAlgorithm02({ typed, equalScalar }) - const algorithm08 = createAlgorithm08({ typed, equalScalar }) - const algorithm10 = createAlgorithm10({ typed, DenseMatrix }) - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) + const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) + const matAlgo08xS0Sid = createMatAlgo08xS0Sid({ typed, equalScalar }) + const matAlgo10xSids = createMatAlgo10xSids({ typed, DenseMatrix }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matAlgo14xDs = createMatAlgo14xDs({ typed }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) + const useMatrixForArrayScalar = createUseMatrixForArrayScalar({ typed, matrix }) /** * Bitwise right logical shift of value x by y number of bits, `x >>> y`. @@ -50,83 +52,50 @@ export const createRightLogShift = /* #__PURE__ */ factory(name, dependencies, ( * @return {number | Array | Matrix} `x` zero-filled shifted right `y` times */ - return typed(name, { - - 'number, number': rightLogShiftNumber, - - // 'BigNumber, BigNumber': ..., // TODO: implement BigNumber support for rightLogShift - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm08(x, y, this, false) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm02(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm01(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) + return typed( + name, + { + 'number, number': rightLogShiftNumber, + + // 'BigNumber, BigNumber': ..., // TODO: implement BigNumber support for rightLogShift + + 'SparseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => { + // check scalar + if (equalScalar(y, 0)) { + return x.clone() + } + return matAlgo11xS0s(x, y, self, false) + }), + + 'DenseMatrix, number | BigNumber': typed.referToSelf(self => (x, y) => { + // check scalar + if (equalScalar(y, 0)) { + return x.clone() + } + return matAlgo14xDs(x, y, self, false) + }), + + 'number | BigNumber, SparseMatrix': typed.referToSelf(self => (x, y) => { + // check scalar + if (equalScalar(x, 0)) { + return zeros(y.size(), y.storage()) + } + return matAlgo10xSids(y, x, self, true) + }), + + 'number | BigNumber, DenseMatrix': typed.referToSelf(self => (x, y) => { + // check scalar + if (equalScalar(x, 0)) { + return zeros(y.size(), y.storage()) + } + return matAlgo14xDs(y, x, self, true) + }) }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, number | BigNumber': function (x, y) { - // check scalar - if (equalScalar(y, 0)) { - return x.clone() - } - return algorithm11(x, y, this, false) - }, - - 'DenseMatrix, number | BigNumber': function (x, y) { - // check scalar - if (equalScalar(y, 0)) { - return x.clone() - } - return algorithm14(x, y, this, false) - }, - - 'number | BigNumber, SparseMatrix': function (x, y) { - // check scalar - if (equalScalar(x, 0)) { - return zeros(y.size(), y.storage()) - } - return algorithm10(y, x, this, true) - }, - - 'number | BigNumber, DenseMatrix': function (x, y) { - // check scalar - if (equalScalar(x, 0)) { - return zeros(y.size(), y.storage()) - } - return algorithm14(y, x, this, true) - }, - - 'Array, number | BigNumber': function (x, y) { - // use matrix implementation - return this(matrix(x), y).valueOf() - }, - - 'number | BigNumber, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)).valueOf() - } - }) + useMatrixForArrayScalar, + matrixAlgorithmSuite({ + SS: matAlgo08xS0Sid, + DS: matAlgo01xDSid, + SD: matAlgo02xDS0 + }) + ) }) diff --git a/src/function/bitwise/useMatrixForArrayScalar.js b/src/function/bitwise/useMatrixForArrayScalar.js new file mode 100644 index 0000000000..ff7f231e2f --- /dev/null +++ b/src/function/bitwise/useMatrixForArrayScalar.js @@ -0,0 +1,15 @@ +import { factory } from '../../utils/factory.js' + +export const createUseMatrixForArrayScalar = /* #__PURE__ */ factory('useMatrixForArrayScalar', ['typed', 'matrix'], ({ typed, matrix }) => ({ + 'Array, number': typed.referTo('DenseMatrix, number', + selfDn => (x, y) => selfDn(matrix(x), y).valueOf()), + + 'Array, BigNumber': typed.referTo('DenseMatrix, BigNumber', + selfDB => (x, y) => selfDB(matrix(x), y).valueOf()), + + 'number, Array': typed.referTo('number, DenseMatrix', + selfnD => (x, y) => selfnD(x, matrix(y)).valueOf()), + + 'BigNumber, Array': typed.referTo('BigNumber, DenseMatrix', + selfBD => (x, y) => selfBD(x, matrix(y)).valueOf()) +})) diff --git a/src/function/complex/arg.js b/src/function/complex/arg.js index 67b4c278ef..52164581b8 100644 --- a/src/function/complex/arg.js +++ b/src/function/complex/arg.js @@ -47,8 +47,6 @@ export const createArg = /* #__PURE__ */ factory(name, dependencies, ({ typed }) // TODO: implement BigNumber support for function arg - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/function/complex/conj.js b/src/function/complex/conj.js index b94ece8bf7..776c14e622 100644 --- a/src/function/complex/conj.js +++ b/src/function/complex/conj.js @@ -31,20 +31,8 @@ export const createConj = /* #__PURE__ */ factory(name, dependencies, ({ typed } * The complex conjugate of x */ return typed(name, { - number: function (x) { - return x - }, - - BigNumber: function (x) { - return x - }, - - Complex: function (x) { - return x.conjugate() - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'number | BigNumber | Fraction': x => x, + Complex: x => x.conjugate(), + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/function/complex/im.js b/src/function/complex/im.js index b5e5faa465..32c423ccc7 100644 --- a/src/function/complex/im.js +++ b/src/function/complex/im.js @@ -33,24 +33,9 @@ export const createIm = /* #__PURE__ */ factory(name, dependencies, ({ typed }) * @return {number | BigNumber | Array | Matrix} The imaginary part of x */ return typed(name, { - number: function (x) { - return 0 - }, - - BigNumber: function (x) { - return x.mul(0) - }, - - Fraction: function (x) { - return x.mul(0) - }, - - Complex: function (x) { - return x.im - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + number: () => 0, + 'BigNumber | Fraction': x => x.mul(0), + Complex: x => x.im, + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/function/complex/re.js b/src/function/complex/re.js index 3b98ecb108..380c3a32ad 100644 --- a/src/function/complex/re.js +++ b/src/function/complex/re.js @@ -33,24 +33,8 @@ export const createRe = /* #__PURE__ */ factory(name, dependencies, ({ typed }) * @return {number | BigNumber | Array | Matrix} The real part of x */ return typed(name, { - number: function (x) { - return x - }, - - BigNumber: function (x) { - return x - }, - - Fraction: function (x) { - return x - }, - - Complex: function (x) { - return x.re - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'number | BigNumber | Fraction': x => x, + Complex: x => x.re, + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/function/logical/and.js b/src/function/logical/and.js index a20523f34b..7b990655ed 100644 --- a/src/function/logical/and.js +++ b/src/function/logical/and.js @@ -1,9 +1,9 @@ -import { createAlgorithm02 } from '../../type/matrix/utils/algorithm02.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm06 } from '../../type/matrix/utils/algorithm06.js' +import { createMatAlgo02xDS0 } from '../../type/matrix/utils/matAlgo02xDS0.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo14xDs } from '../../type/matrix/utils/matAlgo14xDs.js' +import { createMatAlgo06xS0S0 } from '../../type/matrix/utils/matAlgo06xS0S0.js' import { factory } from '../../utils/factory.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' import { andNumber } from '../../plain/number/index.js' const name = 'and' @@ -16,11 +16,11 @@ const dependencies = [ ] export const createAnd = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, zeros, not }) => { - const algorithm02 = createAlgorithm02({ typed, equalScalar }) - const algorithm06 = createAlgorithm06({ typed, equalScalar }) - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) + const matAlgo06xS0S0 = createMatAlgo06xS0S0({ typed, equalScalar }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matAlgo14xDs = createMatAlgo14xDs({ typed }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Logical `and`. Test whether two values are both defined with a nonzero/nonempty value. @@ -50,97 +50,71 @@ export const createAnd = /* #__PURE__ */ factory(name, dependencies, ({ typed, m * @return {boolean | Array | Matrix} * Returns true when both inputs are defined with a nonzero/nonempty value. */ - return typed(name, { - - 'number, number': andNumber, - - 'Complex, Complex': function (x, y) { - return (x.re !== 0 || x.im !== 0) && (y.re !== 0 || y.im !== 0) - }, - - 'BigNumber, BigNumber': function (x, y) { - return !x.isZero() && !y.isZero() && !x.isNaN() && !y.isNaN() - }, - - 'Unit, Unit': function (x, y) { - return this(x.value || 0, y.value || 0) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm06(x, y, this, false) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm02(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm02(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() + return typed( + name, + { + 'number, number': andNumber, + + 'Complex, Complex': function (x, y) { + return (x.re !== 0 || x.im !== 0) && (y.re !== 0 || y.im !== 0) + }, + + 'BigNumber, BigNumber': function (x, y) { + return !x.isZero() && !y.isZero() && !x.isNaN() && !y.isNaN() + }, + + 'Unit, Unit': typed.referToSelf(self => + (x, y) => self(x.value || 0, y.value || 0)), + + 'SparseMatrix, any': typed.referToSelf(self => (x, y) => { + // check scalar + if (not(y)) { + // return zero matrix + return zeros(x.size(), x.storage()) + } + return matAlgo11xS0s(x, y, self, false) + }), + + 'DenseMatrix, any': typed.referToSelf(self => (x, y) => { + // check scalar + if (not(y)) { + // return zero matrix + return zeros(x.size(), x.storage()) + } + return matAlgo14xDs(x, y, self, false) + }), + + 'any, SparseMatrix': typed.referToSelf(self => (x, y) => { + // check scalar + if (not(x)) { + // return zero matrix + return zeros(x.size(), x.storage()) + } + return matAlgo11xS0s(y, x, self, true) + }), + + 'any, DenseMatrix': typed.referToSelf(self => (x, y) => { + // check scalar + if (not(x)) { + // return zero matrix + return zeros(x.size(), x.storage()) + } + return matAlgo14xDs(y, x, self, true) + }), + + 'Array, any': typed.referToSelf(self => (x, y) => { + // use matrix implementation + return self(matrix(x), y).valueOf() + }), + + 'any, Array': typed.referToSelf(self => (x, y) => { + // use matrix implementation + return self(x, matrix(y)).valueOf() + }) }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - // check scalar - if (not(y)) { - // return zero matrix - return zeros(x.size(), x.storage()) - } - return algorithm11(x, y, this, false) - }, - - 'DenseMatrix, any': function (x, y) { - // check scalar - if (not(y)) { - // return zero matrix - return zeros(x.size(), x.storage()) - } - return algorithm14(x, y, this, false) - }, - - 'any, SparseMatrix': function (x, y) { - // check scalar - if (not(x)) { - // return zero matrix - return zeros(x.size(), x.storage()) - } - return algorithm11(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - // check scalar - if (not(x)) { - // return zero matrix - return zeros(x.size(), x.storage()) - } - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return this(matrix(x), y).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)).valueOf() - } - }) + matrixAlgorithmSuite({ + SS: matAlgo06xS0S0, + DS: matAlgo02xDS0 + }) + ) }) diff --git a/src/function/logical/not.js b/src/function/logical/not.js index aa38beaee7..ab4197f9ed 100644 --- a/src/function/logical/not.js +++ b/src/function/logical/not.js @@ -32,6 +32,8 @@ export const createNot = /* #__PURE__ */ factory(name, dependencies, ({ typed }) * Returns true when input is a zero or empty value. */ return typed(name, { + 'null | undefined': () => true, + number: notNumber, Complex: function (x) { @@ -42,12 +44,8 @@ export const createNot = /* #__PURE__ */ factory(name, dependencies, ({ typed }) return x.isZero() || x.isNaN() }, - Unit: function (x) { - return x.value !== null ? this(x.value) : true - }, + Unit: typed.referToSelf(self => x => typed.find(self, x.valueType())(x.value)), - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/function/logical/or.js b/src/function/logical/or.js index d7539055f0..54562e26b9 100644 --- a/src/function/logical/or.js +++ b/src/function/logical/or.js @@ -1,9 +1,8 @@ -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm05 } from '../../type/matrix/utils/algorithm05.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatAlgo05xSfSf } from '../../type/matrix/utils/matAlgo05xSfSf.js' import { factory } from '../../utils/factory.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' import { orNumber } from '../../plain/number/index.js' const name = 'or' @@ -15,11 +14,10 @@ const dependencies = [ ] export const createOr = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, DenseMatrix }) => { - const algorithm03 = createAlgorithm03({ typed }) - const algorithm05 = createAlgorithm05({ typed, equalScalar }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo05xSfSf = createMatAlgo05xSfSf({ typed, equalScalar }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Logical `or`. Test if at least one value is defined with a nonzero/nonempty value. @@ -49,77 +47,26 @@ export const createOr = /* #__PURE__ */ factory(name, dependencies, ({ typed, ma * @return {boolean | Array | Matrix} * Returns true when one of the inputs is defined with a nonzero/nonempty value. */ - return typed(name, { + return typed( + name, + { + 'number, number': orNumber, - 'number, number': orNumber, + 'Complex, Complex': function (x, y) { + return (x.re !== 0 || x.im !== 0) || (y.re !== 0 || y.im !== 0) + }, - 'Complex, Complex': function (x, y) { - return (x.re !== 0 || x.im !== 0) || (y.re !== 0 || y.im !== 0) - }, - - 'BigNumber, BigNumber': function (x, y) { - return (!x.isZero() && !x.isNaN()) || (!y.isZero() && !y.isNaN()) - }, - - 'Unit, Unit': function (x, y) { - return this(x.value || 0, y.value || 0) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm05(x, y, this) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm03(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm12(x, y, this, false) - }, + 'BigNumber, BigNumber': function (x, y) { + return (!x.isZero() && !x.isNaN()) || (!y.isZero() && !y.isNaN()) + }, - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, this, false) + 'Unit, Unit': typed.referToSelf(self => + (x, y) => self(x.value || 0, y.value || 0)) }, - - 'any, SparseMatrix': function (x, y) { - return algorithm12(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + matrixAlgorithmSuite({ + SS: matAlgo05xSfSf, + DS: matAlgo03xDSf, + Ss: matAlgo12xSfs + }) + ) }) diff --git a/src/function/logical/xor.js b/src/function/logical/xor.js index 6ee5ab05c5..3a45c4450c 100644 --- a/src/function/logical/xor.js +++ b/src/function/logical/xor.js @@ -1,9 +1,8 @@ -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm07 } from '../../type/matrix/utils/algorithm07.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo07xSSf } from '../../type/matrix/utils/matAlgo07xSSf.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' import { factory } from '../../utils/factory.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' import { xorNumber } from '../../plain/number/index.js' const name = 'xor' @@ -14,11 +13,10 @@ const dependencies = [ ] export const createXor = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, DenseMatrix }) => { - const algorithm03 = createAlgorithm03({ typed }) - const algorithm07 = createAlgorithm07({ typed, DenseMatrix }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo07xSSf = createMatAlgo07xSSf({ typed, DenseMatrix }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Logical `xor`. Test whether one and only one value is defined with a nonzero/nonempty value. @@ -48,77 +46,26 @@ export const createXor = /* #__PURE__ */ factory(name, dependencies, ({ typed, m * @return {boolean | Array | Matrix} * Returns true when one and only one input is defined with a nonzero/nonempty value. */ - return typed(name, { + return typed( + name, + { + 'number, number': xorNumber, - 'number, number': xorNumber, + 'Complex, Complex': function (x, y) { + return ((x.re !== 0 || x.im !== 0) !== (y.re !== 0 || y.im !== 0)) + }, - 'Complex, Complex': function (x, y) { - return ((x.re !== 0 || x.im !== 0) !== (y.re !== 0 || y.im !== 0)) - }, - - 'BigNumber, BigNumber': function (x, y) { - return ((!x.isZero() && !x.isNaN()) !== (!y.isZero() && !y.isNaN())) - }, - - 'Unit, Unit': function (x, y) { - return this(x.value || 0, y.value || 0) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm07(x, y, this) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm03(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm12(x, y, this, false) - }, + 'BigNumber, BigNumber': function (x, y) { + return ((!x.isZero() && !x.isNaN()) !== (!y.isZero() && !y.isNaN())) + }, - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, this, false) + 'Unit, Unit': typed.referToSelf(self => + (x, y) => self(x.value || 0, y.value || 0)) }, - - 'any, SparseMatrix': function (x, y) { - return algorithm12(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + matrixAlgorithmSuite({ + SS: matAlgo07xSSf, + DS: matAlgo03xDSf, + Ss: matAlgo12xSfs + }) + ) }) diff --git a/src/function/matrix/diff.js b/src/function/matrix/diff.js index 40ce422188..b604771e6c 100644 --- a/src/function/matrix/diff.js +++ b/src/function/matrix/diff.js @@ -68,9 +68,10 @@ export const createDiff = /* #__PURE__ */ factory(name, dependencies, ({ typed, return _recursive(arr, dim) } }, - 'Array | Matrix, BigNumber': function (arr, dim) { - return this(arr, number(dim)) - } + 'Array, BigNumber': typed.referTo('Array,number', selfAn => + (arr, dim) => selfAn(arr, number(dim))), + 'Matrix, BigNumber': typed.referTo('Matrix,number', selfMn => + (arr, dim) => selfMn(arr, number(dim))) }) /** @@ -110,9 +111,6 @@ export const createDiff = /* #__PURE__ */ factory(name, dependencies, ({ typed, function _diff (arr) { const result = [] const size = arr.length - if (size < 2) { - return arr - } for (let i = 1; i < size; i++) { result.push(_ElementDiff(arr[i - 1], arr[i])) } diff --git a/src/function/matrix/ones.js b/src/function/matrix/ones.js index 7e501304c9..5bd55b7719 100644 --- a/src/function/matrix/ones.js +++ b/src/function/matrix/ones.js @@ -24,6 +24,7 @@ export const createOnes = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * Examples: * + * math.ones() // returns [] * math.ones(3) // returns [1, 1, 1] * math.ones(3, 2) // returns [[1, 1], [1, 1], [1, 1]] * math.ones(3, 2, 'dense') // returns Dense Matrix [[1, 1], [1, 1], [1, 1]] @@ -35,7 +36,7 @@ export const createOnes = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * zeros, identity, size, range * - * @param {...number | Array} size The size of each dimension of the matrix + * @param {...(number|BigNumber) | Array} size The size of each dimension of the matrix * @param {string} [format] The Matrix storage format * * @return {Array | Matrix | number} A matrix filled with ones diff --git a/src/function/matrix/sqrtm.js b/src/function/matrix/sqrtm.js index 3df014b8bb..e216d976b0 100644 --- a/src/function/matrix/sqrtm.js +++ b/src/function/matrix/sqrtm.js @@ -4,9 +4,9 @@ import { arraySize } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'sqrtm' -const dependencies = ['typed', 'abs', 'add', 'multiply', 'sqrt', 'subtract', 'inv', 'size', 'max', 'identity'] +const dependencies = ['typed', 'abs', 'add', 'multiply', 'map', 'sqrt', 'subtract', 'inv', 'size', 'max', 'identity'] -export const createSqrtm = /* #__PURE__ */ factory(name, dependencies, ({ typed, abs, add, multiply, sqrt, subtract, inv, size, max, identity }) => { +export const createSqrtm = /* #__PURE__ */ factory(name, dependencies, ({ typed, abs, add, multiply, map, sqrt, subtract, inv, size, max, identity }) => { const _maxIterations = 1e3 const _tolerance = 1e-6 @@ -69,7 +69,7 @@ export const createSqrtm = /* #__PURE__ */ factory(name, dependencies, ({ typed, case 1: // Single element Array | Matrix if (size[0] === 1) { - return sqrt(A) + return map(A, sqrt) } else { throw new RangeError('Matrix must be square ' + '(size: ' + format(size) + ')') diff --git a/src/function/matrix/transpose.js b/src/function/matrix/transpose.js index 709e6794cd..436add4e9a 100644 --- a/src/function/matrix/transpose.js +++ b/src/function/matrix/transpose.js @@ -28,63 +28,56 @@ export const createTranspose = /* #__PURE__ */ factory(name, dependencies, ({ ty * @param {Array | Matrix} x Matrix to be transposed * @return {Array | Matrix} The transposed matrix */ - return typed('transpose', { - - Array: function (x) { - // use dense matrix implementation - return this(matrix(x)).valueOf() - }, - - Matrix: function (x) { - // matrix size - const size = x.size() + return typed(name, { + Array: x => transposeMatrix(matrix(x)).valueOf(), + Matrix: transposeMatrix, + any: clone // scalars + }) - // result - let c + function transposeMatrix (x) { + // matrix size + const size = x.size() - // process dimensions - switch (size.length) { - case 1: - // vector - c = x.clone() - break + // result + let c - case 2: - { - // rows and columns - const rows = size[0] - const columns = size[1] + // process dimensions + switch (size.length) { + case 1: + // vector + c = x.clone() + break - // check columns - if (columns === 0) { - // throw exception - throw new RangeError('Cannot transpose a 2D matrix with no columns (size: ' + format(size) + ')') - } + case 2: + { + // rows and columns + const rows = size[0] + const columns = size[1] - // process storage format - switch (x.storage()) { - case 'dense': - c = _denseTranspose(x, rows, columns) - break - case 'sparse': - c = _sparseTranspose(x, rows, columns) - break - } + // check columns + if (columns === 0) { + // throw exception + throw new RangeError('Cannot transpose a 2D matrix with no columns (size: ' + format(size) + ')') } - break - default: - // multi dimensional - throw new RangeError('Matrix must be a vector or two dimensional (size: ' + format(this._size) + ')') - } - return c - }, + // process storage format + switch (x.storage()) { + case 'dense': + c = _denseTranspose(x, rows, columns) + break + case 'sparse': + c = _sparseTranspose(x, rows, columns) + break + } + } + break - // scalars - any: function (x) { - return clone(x) + default: + // multi dimensional + throw new RangeError('Matrix must be a vector or two dimensional (size: ' + format(size) + ')') } - }) + return c + } function _denseTranspose (m, rows, columns) { // matrix array diff --git a/src/function/matrix/zeros.js b/src/function/matrix/zeros.js index eb53ceb41e..95f5d79129 100644 --- a/src/function/matrix/zeros.js +++ b/src/function/matrix/zeros.js @@ -22,6 +22,7 @@ export const createZeros = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * Examples: * + * math.zeros() // returns [] * math.zeros(3) // returns [0, 0, 0] * math.zeros(3, 2) // returns [[0, 0], [0, 0], [0, 0]] * math.zeros(3, 'dense') // returns [0, 0, 0] @@ -33,7 +34,7 @@ export const createZeros = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * ones, identity, size, range * - * @param {...number | Array} size The size of each dimension of the matrix + * @param {...(number|BigNumber) | Array} size The size of each dimension of the matrix * @param {string} [format] The Matrix storage format * * @return {Array | Matrix} A matrix filled with zeros diff --git a/src/function/probability/factorial.js b/src/function/probability/factorial.js index f4d6b390d7..081f5e702d 100644 --- a/src/function/probability/factorial.js +++ b/src/function/probability/factorial.js @@ -44,8 +44,6 @@ export const createFactorial = /* #__PURE__ */ factory(name, dependencies, ({ ty return gamma(n.plus(1)) }, - 'Array | Matrix': function (n) { - return deepMap(n, this) - } + 'Array | Matrix': typed.referToSelf(self => n => deepMap(n, self)) }) }) diff --git a/src/function/probability/gamma.js b/src/function/probability/gamma.js index 614914fb60..2fb37729b6 100644 --- a/src/function/probability/gamma.js +++ b/src/function/probability/gamma.js @@ -1,4 +1,3 @@ -import { deepMap } from '../../utils/collection.js' import { factory } from '../../utils/factory.js' import { gammaG, gammaNumber, gammaP } from '../../plain/number/index.js' @@ -10,7 +9,8 @@ export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({ typed, * Compute the gamma function of a value using Lanczos approximation for * small values, and an extended Stirling approximation for large values. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix Gamma function, this function does + * not apply to matrices. * * Syntax: * @@ -26,56 +26,55 @@ export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * combinations, factorial, permutations * - * @param {number | Array | Matrix} n A real or complex number - * @return {number | Array | Matrix} The gamma of `n` + * @param {number | BigNumber | Complex} n A real or complex number + * @return {number | BigNumber | Complex} The gamma of `n` */ - return typed(name, { - - number: gammaNumber, - - Complex: function (n) { - if (n.im === 0) { - return this(n.re) - } + function gammaComplex (n) { + if (n.im === 0) { + return gammaNumber(n.re) + } - // Lanczos approximation doesn't work well with real part lower than 0.5 - // So reflection formula is required - if (n.re < 0.5) { // Euler's reflection formula - // gamma(1-z) * gamma(z) = PI / sin(PI * z) - // real part of Z should not be integer [sin(PI) == 0 -> 1/0 - undefined] - // thanks to imperfect sin implementation sin(PI * n) != 0 - // we can safely use it anyway - const t = new Complex(1 - n.re, -n.im) - const r = new Complex(Math.PI * n.re, Math.PI * n.im) - - return new Complex(Math.PI).div(r.sin()).div(this(t)) - } + // Lanczos approximation doesn't work well with real part lower than 0.5 + // So reflection formula is required + if (n.re < 0.5) { // Euler's reflection formula + // gamma(1-z) * gamma(z) = PI / sin(PI * z) + // real part of Z should not be integer [sin(PI) == 0 -> 1/0 - undefined] + // thanks to imperfect sin implementation sin(PI * n) != 0 + // we can safely use it anyway + const t = new Complex(1 - n.re, -n.im) + const r = new Complex(Math.PI * n.re, Math.PI * n.im) + + return new Complex(Math.PI).div(r.sin()).div(gammaComplex(t)) + } - // Lanczos approximation - // z -= 1 - n = new Complex(n.re - 1, n.im) - - // x = gammaPval[0] - let x = new Complex(gammaP[0], 0) - // for (i, gammaPval) in enumerate(gammaP): - for (let i = 1; i < gammaP.length; ++i) { - // x += gammaPval / (z + i) - const gammaPval = new Complex(gammaP[i], 0) - x = x.add(gammaPval.div(n.add(i))) - } - // t = z + gammaG + 0.5 - const t = new Complex(n.re + gammaG + 0.5, n.im) + // Lanczos approximation + // z -= 1 + n = new Complex(n.re - 1, n.im) + + // x = gammaPval[0] + let x = new Complex(gammaP[0], 0) + // for (i, gammaPval) in enumerate(gammaP): + for (let i = 1; i < gammaP.length; ++i) { + // x += gammaPval / (z + i) + const gammaPval = new Complex(gammaP[i], 0) + x = x.add(gammaPval.div(n.add(i))) + } + // t = z + gammaG + 0.5 + const t = new Complex(n.re + gammaG + 0.5, n.im) - // y = sqrt(2 * pi) * t ** (z + 0.5) * exp(-t) * x - const twoPiSqrt = Math.sqrt(2 * Math.PI) - const tpow = t.pow(n.add(0.5)) - const expt = t.neg().exp() + // y = sqrt(2 * pi) * t ** (z + 0.5) * exp(-t) * x + const twoPiSqrt = Math.sqrt(2 * Math.PI) + const tpow = t.pow(n.add(0.5)) + const expt = t.neg().exp() - // y = [x] * [sqrt(2 * pi)] * [t ** (z + 0.5)] * [exp(-t)] - return x.mul(twoPiSqrt).mul(tpow).mul(expt) - }, + // y = [x] * [sqrt(2 * pi)] * [t ** (z + 0.5)] * [exp(-t)] + return x.mul(twoPiSqrt).mul(tpow).mul(expt) + } + return typed(name, { + number: gammaNumber, + Complex: gammaComplex, BigNumber: function (n) { if (n.isInteger()) { return (n.isNegative() || n.isZero()) @@ -88,10 +87,6 @@ export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({ typed, } throw new Error('Integer BigNumber expected') - }, - - 'Array | Matrix': function (n) { - return deepMap(n, this) } }) diff --git a/src/function/probability/kldivergence.js b/src/function/probability/kldivergence.js index 7eae4d683b..a6882a97ea 100644 --- a/src/function/probability/kldivergence.js +++ b/src/function/probability/kldivergence.js @@ -1,9 +1,9 @@ import { factory } from '../../utils/factory.js' const name = 'kldivergence' -const dependencies = ['typed', 'matrix', 'divide', 'sum', 'multiply', 'dotDivide', 'log', 'isNumeric'] +const dependencies = ['typed', 'matrix', 'divide', 'sum', 'multiply', 'map', 'dotDivide', 'log', 'isNumeric'] -export const createKldivergence = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, divide, sum, multiply, dotDivide, log, isNumeric }) => { +export const createKldivergence = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, divide, sum, multiply, map, dotDivide, log, isNumeric }) => { /** * Calculate the Kullback-Leibler (KL) divergence between two distributions * @@ -67,7 +67,7 @@ export const createKldivergence = /* #__PURE__ */ factory(name, dependencies, ({ const qnorm = divide(q, sum(q)) const pnorm = divide(p, sum(p)) - const result = sum(multiply(qnorm, log(dotDivide(qnorm, pnorm)))) + const result = sum(multiply(qnorm, map(dotDivide(qnorm, pnorm), x => log(x)))) if (isNumeric(result)) { return result } else { diff --git a/src/function/probability/lgamma.js b/src/function/probability/lgamma.js index 6f474031a0..b1780baef1 100644 --- a/src/function/probability/lgamma.js +++ b/src/function/probability/lgamma.js @@ -58,42 +58,39 @@ export const createLgamma = /* #__PURE__ */ factory(name, dependencies, ({ Compl * @param {number | Complex} n A real or complex number * @return {number | Complex} The log gamma of `n` */ - return typed(name, { number: lgammaNumber, - - Complex: function (n) { - const TWOPI = 6.2831853071795864769252842 // 2*pi - const LOGPI = 1.1447298858494001741434262 // log(pi) - - const REFLECTION = 0.1 - - if (n.isNaN()) { - return new Complex(NaN, NaN) - } else if (n.im === 0) { - return new Complex(lgammaNumber(n.re), 0) - } else if (n.re >= SMALL_RE || Math.abs(n.im) >= SMALL_IM) { - return lgammaStirling(n) - } else if (n.re <= REFLECTION) { - // Reflection formula. see Proposition 3.1 in [1] - const tmp = copysign(TWOPI, n.im) * Math.floor(0.5 * n.re + 0.25) - // TODO: `complex.js sin` doesn't have extremely high precision, so this value `a` may lose a little precision, - // causing the computation results to be less accurate than the lgamma of real numbers - const a = n.mul(Math.PI).sin().log() - const b = this(new Complex(1 - n.re, -n.im)) - return new Complex(LOGPI, tmp).sub(a).sub(b) - } else if (n.im >= 0) { - return lgammaRecurrence(n) - } else { - return lgammaRecurrence(n.conjugate()).conjugate() - } - }, - + Complex: lgammaComplex, BigNumber: function () { throw new Error("mathjs doesn't yet provide an implementation of the algorithm lgamma for BigNumber") } }) + function lgammaComplex (n) { + const TWOPI = 6.2831853071795864769252842 // 2*pi + const LOGPI = 1.1447298858494001741434262 // log(pi) + + const REFLECTION = 0.1 + + if (n.isNaN()) { + return new Complex(NaN, NaN) + } else if (n.im === 0) { + return new Complex(lgammaNumber(n.re), 0) + } else if (n.re >= SMALL_RE || Math.abs(n.im) >= SMALL_IM) { + return lgammaStirling(n) + } else if (n.re <= REFLECTION) { + // Reflection formula. see Proposition 3.1 in [1] + const tmp = copysign(TWOPI, n.im) * Math.floor(0.5 * n.re + 0.25) + const a = n.mul(Math.PI).sin().log() + const b = lgammaComplex(new Complex(1 - n.re, -n.im)) + return new Complex(LOGPI, tmp).sub(a).sub(b) + } else if (n.im >= 0) { + return lgammaRecurrence(n) + } else { + return lgammaRecurrence(n.conjugate()).conjugate() + } + } + function lgammaStirling (z) { // formula ref in [2] // computation ref: diff --git a/src/function/relational/compare.js b/src/function/relational/compare.js index 0078dd9893..ff57320488 100644 --- a/src/function/relational/compare.js +++ b/src/function/relational/compare.js @@ -1,11 +1,11 @@ import { nearlyEqual as bigNearlyEqual } from '../../utils/bignumber/nearlyEqual.js' import { nearlyEqual } from '../../utils/number.js' import { factory } from '../../utils/factory.js' -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm05 } from '../../type/matrix/utils/algorithm05.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatAlgo05xSfSf } from '../../type/matrix/utils/matAlgo05xSfSf.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' +import { createCompareUnits } from './compareUnits.js' const name = 'compare' const dependencies = [ @@ -19,11 +19,11 @@ const dependencies = [ ] export const createCompare = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, equalScalar, matrix, BigNumber, Fraction, DenseMatrix }) => { - const algorithm03 = createAlgorithm03({ typed }) - const algorithm05 = createAlgorithm05({ typed, equalScalar }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo05xSfSf = createMatAlgo05xSfSf({ typed, equalScalar }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) + const compareUnits = createCompareUnits({ typed }) /** * Compare two values. Returns 1 when x > y, -1 when x < y, and 0 when x == y. @@ -62,96 +62,35 @@ export const createCompare = /* #__PURE__ */ factory(name, dependencies, ({ type * @return {number | BigNumber | Fraction | Array | Matrix} Returns the result of the comparison: * 1 when x > y, -1 when x < y, and 0 when x == y. */ - return typed(name, { - - 'boolean, boolean': function (x, y) { - return x === y ? 0 : (x > y ? 1 : -1) - }, - - 'number, number': function (x, y) { - return nearlyEqual(x, y, config.epsilon) - ? 0 - : (x > y ? 1 : -1) - }, - - 'BigNumber, BigNumber': function (x, y) { - return bigNearlyEqual(x, y, config.epsilon) - ? new BigNumber(0) - : new BigNumber(x.cmp(y)) - }, - - 'Fraction, Fraction': function (x, y) { - return new Fraction(x.compare(y)) - }, - - 'Complex, Complex': function () { - throw new TypeError('No ordering relation is defined for complex numbers') - }, - - 'Unit, Unit': function (x, y) { - if (!x.equalBase(y)) { - throw new Error('Cannot compare units with different base') + return typed( + name, + createCompareNumber({ typed, config }), + { + 'boolean, boolean': function (x, y) { + return x === y ? 0 : (x > y ? 1 : -1) + }, + + 'BigNumber, BigNumber': function (x, y) { + return bigNearlyEqual(x, y, config.epsilon) + ? new BigNumber(0) + : new BigNumber(x.cmp(y)) + }, + + 'Fraction, Fraction': function (x, y) { + return new Fraction(x.compare(y)) + }, + + 'Complex, Complex': function () { + throw new TypeError('No ordering relation is defined for complex numbers') } - return this(x.value, y.value) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm05(x, y, this) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm03(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, this, false) }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm12(x, y, this, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm12(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + compareUnits, + matrixAlgorithmSuite({ + SS: matAlgo05xSfSf, + DS: matAlgo03xDSf, + Ss: matAlgo12xSfs + }) + ) }) export const createCompareNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { diff --git a/src/function/relational/compareNatural.js b/src/function/relational/compareNatural.js index 19e8558b3e..9e882ad46e 100644 --- a/src/function/relational/compareNatural.js +++ b/src/function/relational/compareNatural.js @@ -79,77 +79,77 @@ export const createCompareNatural = /* #__PURE__ */ factory(name, dependencies, * @return {number} Returns the result of the comparison: * 1 when x > y, -1 when x < y, and 0 when x == y. */ - return typed(name, { - 'any, any': function (x, y) { - const typeX = typeOf(x) - const typeY = typeOf(y) - let c + return typed(name, { 'any, any': _compareNatural }) // just to check # args - // numeric types - if ((typeX === 'number' || typeX === 'BigNumber' || typeX === 'Fraction') && - (typeY === 'number' || typeY === 'BigNumber' || typeY === 'Fraction')) { - c = compare(x, y) - if (c.toString() !== '0') { - // c can be number, BigNumber, or Fraction - return c > 0 ? 1 : -1 // return a number - } else { - return naturalSort(typeX, typeY) - } - } + function _compareNatural (x, y) { + const typeX = typeOf(x) + const typeY = typeOf(y) + let c - // matrix types - if (typeX === 'Array' || typeX === 'Matrix' || - typeY === 'Array' || typeY === 'Matrix') { - c = compareMatricesAndArrays(this, x, y) - if (c !== 0) { - return c - } else { - return naturalSort(typeX, typeY) - } + // numeric types + if ((typeX === 'number' || typeX === 'BigNumber' || typeX === 'Fraction') && + (typeY === 'number' || typeY === 'BigNumber' || typeY === 'Fraction')) { + c = compare(x, y) + if (c.toString() !== '0') { + // c can be number, BigNumber, or Fraction + return c > 0 ? 1 : -1 // return a number + } else { + return naturalSort(typeX, typeY) } + } - // in case of different types, order by name of type, i.e. 'BigNumber' < 'Complex' - if (typeX !== typeY) { + // matrix types + const matTypes = ['Array', 'DenseMatrix', 'SparseMatrix'] + if (matTypes.includes(typeX) || matTypes.includes(typeY)) { + c = compareMatricesAndArrays(_compareNatural, x, y) + if (c !== 0) { + return c + } else { return naturalSort(typeX, typeY) } + } - if (typeX === 'Complex') { - return compareComplexNumbers(x, y) - } + // in case of different types, order by name of type, i.e. 'BigNumber' < 'Complex' + if (typeX !== typeY) { + return naturalSort(typeX, typeY) + } - if (typeX === 'Unit') { - if (x.equalBase(y)) { - return this(x.value, y.value) - } + if (typeX === 'Complex') { + return compareComplexNumbers(x, y) + } - // compare by units - return compareArrays(this, x.formatUnits(), y.formatUnits()) + if (typeX === 'Unit') { + if (x.equalBase(y)) { + return _compareNatural(x.value, y.value) } - if (typeX === 'boolean') { - return compareBooleans(x, y) - } + // compare by units + return compareArrays(_compareNatural, x.formatUnits(), y.formatUnits()) + } - if (typeX === 'string') { - return naturalSort(x, y) - } + if (typeX === 'boolean') { + return compareBooleans(x, y) + } - if (typeX === 'Object') { - return compareObjects(this, x, y) - } + if (typeX === 'string') { + return naturalSort(x, y) + } - if (typeX === 'null') { - return 0 - } + if (typeX === 'Object') { + return compareObjects(_compareNatural, x, y) + } - if (typeX === 'undefined') { - return 0 - } + if (typeX === 'null') { + return 0 + } - // this should not occur... - throw new TypeError('Unsupported type of value "' + typeX + '"') + if (typeX === 'undefined') { + return 0 } - }) + + // this should not occur... + throw new TypeError('Unsupported type of value "' + typeX + '"') + } /** * Compare mixed matrix/array types, by converting to same-shaped array. diff --git a/src/function/relational/compareText.js b/src/function/relational/compareText.js index 05b7553e17..754db4ec55 100644 --- a/src/function/relational/compareText.js +++ b/src/function/relational/compareText.js @@ -1,7 +1,6 @@ import { compareText as _compareText } from '../../utils/string.js' import { factory } from '../../utils/factory.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'compareText' const dependencies = [ @@ -9,9 +8,10 @@ const dependencies = [ 'matrix' ] +_compareText.signature = 'any, any' + export const createCompareText = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix }) => { - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Compare two strings lexically. Comparison is case sensitive. @@ -41,51 +41,12 @@ export const createCompareText = /* #__PURE__ */ factory(name, dependencies, ({ * @return {number | Array | DenseMatrix} Returns the result of the comparison: * 1 when x > y, -1 when x < y, and 0 when x == y. */ - return typed(name, { - - 'any, any': _compareText, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, _compareText) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, _compareText, false) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, _compareText, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, _compareText, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, _compareText, true).valueOf() - } - }) + return typed(name, _compareText, matrixAlgorithmSuite({ + elop: _compareText, + Ds: true + })) }) -export const createCompareTextNumber = /* #__PURE__ */ factory(name, ['typed'], ({ typed }) => { - return typed(name, { - 'any, any': _compareText - }) -}) +export const createCompareTextNumber = /* #__PURE__ */ factory( + name, ['typed'], ({ typed }) => typed(name, _compareText) +) diff --git a/src/function/relational/compareUnits.js b/src/function/relational/compareUnits.js new file mode 100644 index 0000000000..5b4937d26b --- /dev/null +++ b/src/function/relational/compareUnits.js @@ -0,0 +1,12 @@ +import { factory } from '../../utils/factory.js' + +export const createCompareUnits = /* #__PURE__ */ factory( + 'compareUnits', ['typed'], ({ typed }) => ({ + 'Unit, Unit': typed.referToSelf(self => (x, y) => { + if (!x.equalBase(y)) { + throw new Error('Cannot compare units with different base') + } + return typed.find(self, [x.valueType(), y.valueType()])(x.value, y.value) + }) + }) +) diff --git a/src/function/relational/equal.js b/src/function/relational/equal.js index 94f294e050..62ec1d7c3f 100644 --- a/src/function/relational/equal.js +++ b/src/function/relational/equal.js @@ -1,9 +1,8 @@ import { factory } from '../../utils/factory.js' -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm07 } from '../../type/matrix/utils/algorithm07.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo07xSSf } from '../../type/matrix/utils/matAlgo07xSSf.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'equal' const dependencies = [ @@ -14,11 +13,10 @@ const dependencies = [ ] export const createEqual = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, DenseMatrix }) => { - const algorithm03 = createAlgorithm03({ typed }) - const algorithm07 = createAlgorithm07({ typed, DenseMatrix }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo07xSSf = createMatAlgo07xSSf({ typed, DenseMatrix }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Test whether two values are equal. @@ -64,75 +62,16 @@ export const createEqual = /* #__PURE__ */ factory(name, dependencies, ({ typed, * @param {number | BigNumber | boolean | Complex | Unit | string | Array | Matrix} y Second value to compare * @return {boolean | Array | Matrix} Returns true when the compared values are equal, else returns false */ - return typed(name, { - - 'any, any': function (x, y) { - // strict equality for null and undefined? - if (x === null) { return y === null } - if (y === null) { return x === null } - if (x === undefined) { return y === undefined } - if (y === undefined) { return x === undefined } - - return equalScalar(x, y) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm07(x, y, equalScalar) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm03(y, x, equalScalar, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, equalScalar, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, equalScalar) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm12(x, y, equalScalar, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, equalScalar, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm12(y, x, equalScalar, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, equalScalar, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, equalScalar, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, equalScalar, true).valueOf() - } - }) + return typed( + name, + createEqualNumber({ typed, equalScalar }), + matrixAlgorithmSuite({ + elop: equalScalar, + SS: matAlgo07xSSf, + DS: matAlgo03xDSf, + Ss: matAlgo12xSfs + }) + ) }) export const createEqualNumber = factory(name, ['typed', 'equalScalar'], ({ typed, equalScalar }) => { diff --git a/src/function/relational/equalScalar.js b/src/function/relational/equalScalar.js index cedfc085ca..e51f49f489 100644 --- a/src/function/relational/equalScalar.js +++ b/src/function/relational/equalScalar.js @@ -2,11 +2,14 @@ import { nearlyEqual as bigNearlyEqual } from '../../utils/bignumber/nearlyEqual import { nearlyEqual } from '../../utils/number.js' import { factory } from '../../utils/factory.js' import { complexEquals } from '../../utils/complex.js' +import { createCompareUnits } from './compareUnits.js' const name = 'equalScalar' const dependencies = ['typed', 'config'] export const createEqualScalar = /* #__PURE__ */ factory(name, dependencies, ({ typed, config }) => { + const compareUnits = createCompareUnits({ typed }) + /** * Test whether two scalar values are nearly equal. * @@ -35,15 +38,8 @@ export const createEqualScalar = /* #__PURE__ */ factory(name, dependencies, ({ 'Complex, Complex': function (x, y) { return complexEquals(x, y, config.epsilon) - }, - - 'Unit, Unit': function (x, y) { - if (!x.equalBase(y)) { - throw new Error('Cannot compare units with different base') - } - return this(x.value, y.value) } - }) + }, compareUnits) }) export const createEqualScalarNumber = factory(name, ['typed', 'config'], ({ typed, config }) => { diff --git a/src/function/relational/larger.js b/src/function/relational/larger.js index 1f9f04735a..2350519373 100644 --- a/src/function/relational/larger.js +++ b/src/function/relational/larger.js @@ -1,11 +1,11 @@ import { nearlyEqual as bigNearlyEqual } from '../../utils/bignumber/nearlyEqual.js' import { nearlyEqual } from '../../utils/number.js' import { factory } from '../../utils/factory.js' -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm07 } from '../../type/matrix/utils/algorithm07.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo07xSSf } from '../../type/matrix/utils/matAlgo07xSSf.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' +import { createCompareUnits } from './compareUnits.js' const name = 'larger' const dependencies = [ @@ -16,11 +16,11 @@ const dependencies = [ ] export const createLarger = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, matrix, DenseMatrix }) => { - const algorithm03 = createAlgorithm03({ typed }) - const algorithm07 = createAlgorithm07({ typed, DenseMatrix }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo07xSSf = createMatAlgo07xSSf({ typed, DenseMatrix }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) + const compareUnits = createCompareUnits({ typed }) /** * Test whether value x is larger than y. @@ -53,92 +53,29 @@ export const createLarger = /* #__PURE__ */ factory(name, dependencies, ({ typed * @param {number | BigNumber | Fraction | boolean | Unit | string | Array | Matrix} y Second value to compare * @return {boolean | Array | Matrix} Returns true when the x is larger than y, else returns false */ - return typed(name, { - - 'boolean, boolean': function (x, y) { - return x > y - }, - - 'number, number': function (x, y) { - return x > y && !nearlyEqual(x, y, config.epsilon) - }, + return typed( + name, + createLargerNumber({ typed, config }), + { + 'boolean, boolean': (x, y) => x > y, - 'BigNumber, BigNumber': function (x, y) { - return x.gt(y) && !bigNearlyEqual(x, y, config.epsilon) - }, + 'BigNumber, BigNumber': function (x, y) { + return x.gt(y) && !bigNearlyEqual(x, y, config.epsilon) + }, - 'Fraction, Fraction': function (x, y) { - return x.compare(y) === 1 - }, + 'Fraction, Fraction': (x, y) => (x.compare(y) === 1), - 'Complex, Complex': function () { - throw new TypeError('No ordering relation is defined for complex numbers') - }, - - 'Unit, Unit': function (x, y) { - if (!x.equalBase(y)) { - throw new Error('Cannot compare units with different base') + 'Complex, Complex': function () { + throw new TypeError('No ordering relation is defined for complex numbers') } - return this(x.value, y.value) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm07(x, y, this) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm03(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm12(x, y, this, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm12(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + compareUnits, + matrixAlgorithmSuite({ + SS: matAlgo07xSSf, + DS: matAlgo03xDSf, + Ss: matAlgo12xSfs + }) + ) }) export const createLargerNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { diff --git a/src/function/relational/largerEq.js b/src/function/relational/largerEq.js index 85ce692b58..b61a3ba008 100644 --- a/src/function/relational/largerEq.js +++ b/src/function/relational/largerEq.js @@ -1,11 +1,11 @@ import { nearlyEqual as bigNearlyEqual } from '../../utils/bignumber/nearlyEqual.js' import { nearlyEqual } from '../../utils/number.js' import { factory } from '../../utils/factory.js' -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm07 } from '../../type/matrix/utils/algorithm07.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo07xSSf } from '../../type/matrix/utils/matAlgo07xSSf.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' +import { createCompareUnits } from './compareUnits.js' const name = 'largerEq' const dependencies = [ @@ -16,11 +16,11 @@ const dependencies = [ ] export const createLargerEq = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, matrix, DenseMatrix }) => { - const algorithm03 = createAlgorithm03({ typed }) - const algorithm07 = createAlgorithm07({ typed, DenseMatrix }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo07xSSf = createMatAlgo07xSSf({ typed, DenseMatrix }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) + const compareUnits = createCompareUnits({ typed }) /** * Test whether value x is larger or equal to y. @@ -49,92 +49,29 @@ export const createLargerEq = /* #__PURE__ */ factory(name, dependencies, ({ typ * @param {number | BigNumber | Fraction | boolean | Unit | string | Array | Matrix} y Second value to compare * @return {boolean | Array | Matrix} Returns true when the x is larger or equal to y, else returns false */ - return typed(name, { - - 'boolean, boolean': function (x, y) { - return x >= y - }, - - 'number, number': function (x, y) { - return x >= y || nearlyEqual(x, y, config.epsilon) - }, + return typed( + name, + createLargerEqNumber({ typed, config }), + { + 'boolean, boolean': (x, y) => x >= y, - 'BigNumber, BigNumber': function (x, y) { - return x.gte(y) || bigNearlyEqual(x, y, config.epsilon) - }, + 'BigNumber, BigNumber': function (x, y) { + return x.gte(y) || bigNearlyEqual(x, y, config.epsilon) + }, - 'Fraction, Fraction': function (x, y) { - return x.compare(y) !== -1 - }, + 'Fraction, Fraction': (x, y) => (x.compare(y) !== -1), - 'Complex, Complex': function () { - throw new TypeError('No ordering relation is defined for complex numbers') - }, - - 'Unit, Unit': function (x, y) { - if (!x.equalBase(y)) { - throw new Error('Cannot compare units with different base') + 'Complex, Complex': function () { + throw new TypeError('No ordering relation is defined for complex numbers') } - return this(x.value, y.value) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm07(x, y, this) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm03(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm12(x, y, this, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm12(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + compareUnits, + matrixAlgorithmSuite({ + SS: matAlgo07xSSf, + DS: matAlgo03xDSf, + Ss: matAlgo12xSfs + }) + ) }) export const createLargerEqNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { diff --git a/src/function/relational/smaller.js b/src/function/relational/smaller.js index cf0564d418..c3c306db81 100644 --- a/src/function/relational/smaller.js +++ b/src/function/relational/smaller.js @@ -1,11 +1,11 @@ import { nearlyEqual as bigNearlyEqual } from '../../utils/bignumber/nearlyEqual.js' import { nearlyEqual } from '../../utils/number.js' import { factory } from '../../utils/factory.js' -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm07 } from '../../type/matrix/utils/algorithm07.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo07xSSf } from '../../type/matrix/utils/matAlgo07xSSf.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' +import { createCompareUnits } from './compareUnits.js' const name = 'smaller' const dependencies = [ @@ -16,11 +16,11 @@ const dependencies = [ ] export const createSmaller = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, matrix, DenseMatrix }) => { - const algorithm03 = createAlgorithm03({ typed }) - const algorithm07 = createAlgorithm07({ typed, DenseMatrix }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo07xSSf = createMatAlgo07xSSf({ typed, DenseMatrix }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) + const compareUnits = createCompareUnits({ typed }) /** * Test whether value x is smaller than y. @@ -53,92 +53,29 @@ export const createSmaller = /* #__PURE__ */ factory(name, dependencies, ({ type * @param {number | BigNumber | Fraction | boolean | Unit | string | Array | Matrix} y Second value to compare * @return {boolean | Array | Matrix} Returns true when the x is smaller than y, else returns false */ - return typed(name, { - - 'boolean, boolean': function (x, y) { - return x < y - }, - - 'number, number': function (x, y) { - return x < y && !nearlyEqual(x, y, config.epsilon) - }, + return typed( + name, + createSmallerNumber({ typed, config }), + { + 'boolean, boolean': (x, y) => x < y, - 'BigNumber, BigNumber': function (x, y) { - return x.lt(y) && !bigNearlyEqual(x, y, config.epsilon) - }, + 'BigNumber, BigNumber': function (x, y) { + return x.lt(y) && !bigNearlyEqual(x, y, config.epsilon) + }, - 'Fraction, Fraction': function (x, y) { - return x.compare(y) === -1 - }, + 'Fraction, Fraction': (x, y) => (x.compare(y) === -1), - 'Complex, Complex': function (x, y) { - throw new TypeError('No ordering relation is defined for complex numbers') - }, - - 'Unit, Unit': function (x, y) { - if (!x.equalBase(y)) { - throw new Error('Cannot compare units with different base') + 'Complex, Complex': function (x, y) { + throw new TypeError('No ordering relation is defined for complex numbers') } - return this(x.value, y.value) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm07(x, y, this) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm03(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm12(x, y, this, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm12(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + compareUnits, + matrixAlgorithmSuite({ + SS: matAlgo07xSSf, + DS: matAlgo03xDSf, + Ss: matAlgo12xSfs + }) + ) }) export const createSmallerNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { diff --git a/src/function/relational/smallerEq.js b/src/function/relational/smallerEq.js index 3b9b755eb2..f99fe713ef 100644 --- a/src/function/relational/smallerEq.js +++ b/src/function/relational/smallerEq.js @@ -1,11 +1,11 @@ import { nearlyEqual as bigNearlyEqual } from '../../utils/bignumber/nearlyEqual.js' import { nearlyEqual } from '../../utils/number.js' import { factory } from '../../utils/factory.js' -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm07 } from '../../type/matrix/utils/algorithm07.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo07xSSf } from '../../type/matrix/utils/matAlgo07xSSf.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' +import { createCompareUnits } from './compareUnits.js' const name = 'smallerEq' const dependencies = [ @@ -16,11 +16,11 @@ const dependencies = [ ] export const createSmallerEq = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, matrix, DenseMatrix }) => { - const algorithm03 = createAlgorithm03({ typed }) - const algorithm07 = createAlgorithm07({ typed, DenseMatrix }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo07xSSf = createMatAlgo07xSSf({ typed, DenseMatrix }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) + const compareUnits = createCompareUnits({ typed }) /** * Test whether value x is smaller or equal to y. @@ -49,92 +49,29 @@ export const createSmallerEq = /* #__PURE__ */ factory(name, dependencies, ({ ty * @param {number | BigNumber | Fraction | boolean | Unit | string | Array | Matrix} y Second value to compare * @return {boolean | Array | Matrix} Returns true when the x is smaller than y, else returns false */ - return typed(name, { - - 'boolean, boolean': function (x, y) { - return x <= y - }, - - 'number, number': function (x, y) { - return x <= y || nearlyEqual(x, y, config.epsilon) - }, + return typed( + name, + createSmallerEqNumber({ typed, config }), + { + 'boolean, boolean': (x, y) => (x <= y), - 'BigNumber, BigNumber': function (x, y) { - return x.lte(y) || bigNearlyEqual(x, y, config.epsilon) - }, + 'BigNumber, BigNumber': function (x, y) { + return x.lte(y) || bigNearlyEqual(x, y, config.epsilon) + }, - 'Fraction, Fraction': function (x, y) { - return x.compare(y) !== 1 - }, + 'Fraction, Fraction': (x, y) => (x.compare(y) !== 1), - 'Complex, Complex': function () { - throw new TypeError('No ordering relation is defined for complex numbers') - }, - - 'Unit, Unit': function (x, y) { - if (!x.equalBase(y)) { - throw new Error('Cannot compare units with different base') + 'Complex, Complex': function () { + throw new TypeError('No ordering relation is defined for complex numbers') } - return this(x.value, y.value) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm07(x, y, this) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm03(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm12(x, y, this, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm12(y, x, this, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + compareUnits, + matrixAlgorithmSuite({ + SS: matAlgo07xSSf, + DS: matAlgo03xDSf, + Ss: matAlgo12xSfs + }) + ) }) export const createSmallerEqNumber = /* #__PURE__ */ factory(name, ['typed', 'config'], ({ typed, config }) => { diff --git a/src/function/relational/unequal.js b/src/function/relational/unequal.js index 9ba26b8a85..f8454ff113 100644 --- a/src/function/relational/unequal.js +++ b/src/function/relational/unequal.js @@ -1,9 +1,8 @@ import { factory } from '../../utils/factory.js' -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm07 } from '../../type/matrix/utils/algorithm07.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo07xSSf } from '../../type/matrix/utils/matAlgo07xSSf.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'unequal' const dependencies = [ @@ -15,11 +14,10 @@ const dependencies = [ ] export const createUnequal = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, equalScalar, matrix, DenseMatrix }) => { - const algorithm03 = createAlgorithm03({ typed }) - const algorithm07 = createAlgorithm07({ typed, DenseMatrix }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo07xSSf = createMatAlgo07xSSf({ typed, DenseMatrix }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Test whether two values are unequal. @@ -64,75 +62,16 @@ export const createUnequal = /* #__PURE__ */ factory(name, dependencies, ({ type * @param {number | BigNumber | Fraction | boolean | Complex | Unit | string | Array | Matrix | undefined} y Second value to compare * @return {boolean | Array | Matrix} Returns true when the compared values are unequal, else returns false */ - return typed('unequal', { - - 'any, any': function (x, y) { - // strict equality for null and undefined? - if (x === null) { return y !== null } - if (y === null) { return x !== null } - if (x === undefined) { return y !== undefined } - if (y === undefined) { return x !== undefined } - - return _unequal(x, y) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm07(x, y, _unequal) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - return algorithm03(y, x, _unequal, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, _unequal, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, _unequal) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'SparseMatrix, any': function (x, y) { - return algorithm12(x, y, _unequal, false) - }, - - 'DenseMatrix, any': function (x, y) { - return algorithm14(x, y, _unequal, false) - }, - - 'any, SparseMatrix': function (x, y) { - return algorithm12(y, x, _unequal, true) - }, - - 'any, DenseMatrix': function (x, y) { - return algorithm14(y, x, _unequal, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, _unequal, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, _unequal, true).valueOf() - } - }) + return typed( + name, + createUnequalNumber({ typed, equalScalar }), + matrixAlgorithmSuite({ + elop: _unequal, + SS: matAlgo07xSSf, + DS: matAlgo03xDSf, + Ss: matAlgo12xSfs + }) + ) function _unequal (x, y) { return !equalScalar(x, y) diff --git a/src/function/special/erf.js b/src/function/special/erf.js index 0ce180f11b..c829a26f00 100644 --- a/src/function/special/erf.js +++ b/src/function/special/erf.js @@ -50,9 +50,7 @@ export const createErf = /* #__PURE__ */ factory(name, dependencies, ({ typed }) return sign(x) * (1 - erfc3(y)) }, - 'Array | Matrix': function (n) { - return deepMap(n, this) - } + 'Array | Matrix': typed.referToSelf(self => n => deepMap(n, self)) // TODO: For complex numbers, use the approximation for the Faddeeva function // from "More Efficient Computation of the Complex Error Function" (AMS) diff --git a/src/function/statistics/std.js b/src/function/statistics/std.js index f19d282665..7f6d33ba12 100644 --- a/src/function/statistics/std.js +++ b/src/function/statistics/std.js @@ -1,9 +1,9 @@ import { factory } from '../../utils/factory.js' - +import { isCollection } from '../../utils/is.js' const name = 'std' -const dependencies = ['typed', 'sqrt', 'variance'] +const dependencies = ['typed', 'map', 'sqrt', 'variance'] -export const createStd = /* #__PURE__ */ factory(name, dependencies, ({ typed, sqrt, variance }) => { +export const createStd = /* #__PURE__ */ factory(name, dependencies, ({ typed, map, sqrt, variance }) => { /** * Compute the standard deviation of a matrix or a list with values. * The standard deviations is defined as the square root of the variance: @@ -81,7 +81,12 @@ export const createStd = /* #__PURE__ */ factory(name, dependencies, ({ typed, s } try { - return sqrt(variance.apply(null, arguments)) + const v = variance.apply(null, arguments) + if (isCollection(v)) { + return map(v, sqrt) + } else { + return sqrt(v) + } } catch (err) { if (err instanceof TypeError && err.message.indexOf(' variance') !== -1) { throw new TypeError(err.message.replace(' variance', ' std')) diff --git a/src/function/trigonometry/acos.js b/src/function/trigonometry/acos.js index 1882d91048..19b3f8ce5a 100644 --- a/src/function/trigonometry/acos.js +++ b/src/function/trigonometry/acos.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' const name = 'acos' const dependencies = ['typed', 'config', 'Complex'] @@ -8,7 +7,8 @@ export const createAcos = /* #__PURE__ */ factory(name, dependencies, ({ typed, /** * Calculate the inverse cosine of a value. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix arccosine, this function does not + * apply to matrices. * * Syntax: * @@ -25,8 +25,8 @@ export const createAcos = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * cos, atan, asin * - * @param {number | BigNumber | Complex | Array | Matrix} x Function input - * @return {number | BigNumber | Complex | Array | Matrix} The arc cosine of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} The arc cosine of x */ return typed(name, { number: function (x) { @@ -43,10 +43,6 @@ export const createAcos = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber: function (x) { return x.acos() - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) } }) }) diff --git a/src/function/trigonometry/acosh.js b/src/function/trigonometry/acosh.js index 7fbe1d1280..7340d911cd 100644 --- a/src/function/trigonometry/acosh.js +++ b/src/function/trigonometry/acosh.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { acoshNumber } from '../../plain/number/index.js' const name = 'acosh' @@ -24,8 +23,8 @@ export const createAcosh = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * cosh, asinh, atanh * - * @param {number | Complex | Unit | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} Hyperbolic arccosine of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} Hyperbolic arccosine of x */ return typed(name, { number: function (x) { @@ -44,10 +43,6 @@ export const createAcosh = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber: function (x) { return x.acosh() - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) } }) }) diff --git a/src/function/trigonometry/acot.js b/src/function/trigonometry/acot.js index 07dd3166c1..20d51c1068 100644 --- a/src/function/trigonometry/acot.js +++ b/src/function/trigonometry/acot.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { acotNumber } from '../../plain/number/index.js' const name = 'acot' @@ -9,7 +8,8 @@ export const createAcot = /* #__PURE__ */ factory(name, dependencies, ({ typed, /** * Calculate the inverse cotangent of a value, defined as `acot(x) = atan(1/x)`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix arccotanget, this function does not + * apply to matrices. * * Syntax: * @@ -26,8 +26,8 @@ export const createAcot = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * cot, atan * - * @param {number | Complex | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} The arc cotangent of x + * @param {number | BigNumber| Complex} x Function input + * @return {number | BigNumber| Complex} The arc cotangent of x */ return typed(name, { number: acotNumber, @@ -38,10 +38,6 @@ export const createAcot = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber: function (x) { return new BigNumber(1).div(x).atan() - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) } }) }) diff --git a/src/function/trigonometry/acoth.js b/src/function/trigonometry/acoth.js index 96ec9ac188..d339dbb499 100644 --- a/src/function/trigonometry/acoth.js +++ b/src/function/trigonometry/acoth.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { acothNumber } from '../../plain/number/index.js' const name = 'acoth' @@ -10,7 +9,8 @@ export const createAcoth = /* #__PURE__ */ factory(name, dependencies, ({ typed, * Calculate the hyperbolic arccotangent of a value, * defined as `acoth(x) = atanh(1/x) = (ln((x+1)/x) + ln(x/(x-1))) / 2`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix hyperbolic arccotangent, this + * function does not apply to matrices. * * Syntax: * @@ -24,8 +24,8 @@ export const createAcoth = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * acsch, asech * - * @param {number | Complex | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} Hyperbolic arccotangent of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} Hyperbolic arccotangent of x */ return typed(name, { number: function (x) { @@ -41,10 +41,6 @@ export const createAcoth = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber: function (x) { return new BigNumber(1).div(x).atanh() - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) } }) }) diff --git a/src/function/trigonometry/acsc.js b/src/function/trigonometry/acsc.js index 620c44cbd8..a4de3f3fca 100644 --- a/src/function/trigonometry/acsc.js +++ b/src/function/trigonometry/acsc.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { acscNumber } from '../../plain/number/index.js' const name = 'acsc' @@ -9,7 +8,8 @@ export const createAcsc = /* #__PURE__ */ factory(name, dependencies, ({ typed, /** * Calculate the inverse cosecant of a value, defined as `acsc(x) = asin(1/x)`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix arccosecant, this function does not + * apply to matrices. * * Syntax: * @@ -26,8 +26,8 @@ export const createAcsc = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * csc, asin, asec * - * @param {number | Complex | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} The arc cosecant of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} The arc cosecant of x */ return typed(name, { number: function (x) { @@ -43,10 +43,6 @@ export const createAcsc = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber: function (x) { return new BigNumber(1).div(x).asin() - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) } }) }) diff --git a/src/function/trigonometry/acsch.js b/src/function/trigonometry/acsch.js index ff7c3d0232..e287f3c890 100644 --- a/src/function/trigonometry/acsch.js +++ b/src/function/trigonometry/acsch.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { acschNumber } from '../../plain/number/index.js' const name = 'acsch' @@ -10,7 +9,8 @@ export const createAcsch = /* #__PURE__ */ factory(name, dependencies, ({ typed, * Calculate the hyperbolic arccosecant of a value, * defined as `acsch(x) = asinh(1/x) = ln(1/x + sqrt(1/x^2 + 1))`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix hyperbolic arccosecant, this function + * does not apply to matrices. * * Syntax: * @@ -24,8 +24,8 @@ export const createAcsch = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * asech, acoth * - * @param {number | Complex | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} Hyperbolic arccosecant of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} Hyperbolic arccosecant of x */ return typed(name, { number: acschNumber, @@ -36,10 +36,6 @@ export const createAcsch = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber: function (x) { return new BigNumber(1).div(x).asinh() - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) } }) }) diff --git a/src/function/trigonometry/asec.js b/src/function/trigonometry/asec.js index 451094a234..228988f5be 100644 --- a/src/function/trigonometry/asec.js +++ b/src/function/trigonometry/asec.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { asecNumber } from '../../plain/number/index.js' const name = 'asec' @@ -9,7 +8,8 @@ export const createAsec = /* #__PURE__ */ factory(name, dependencies, ({ typed, /** * Calculate the inverse secant of a value. Defined as `asec(x) = acos(1/x)`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix arcsecant, this function does not + * apply to matrices. * * Syntax: * @@ -26,8 +26,8 @@ export const createAsec = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * acos, acot, acsc * - * @param {number | Complex | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} The arc secant of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} The arc secant of x */ return typed(name, { number: function (x) { @@ -43,10 +43,6 @@ export const createAsec = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber: function (x) { return new BigNumber(1).div(x).acos() - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) } }) }) diff --git a/src/function/trigonometry/asech.js b/src/function/trigonometry/asech.js index 7576f952e4..9688adf370 100644 --- a/src/function/trigonometry/asech.js +++ b/src/function/trigonometry/asech.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { asechNumber } from '../../plain/number/index.js' const name = 'asech' @@ -10,7 +9,8 @@ export const createAsech = /* #__PURE__ */ factory(name, dependencies, ({ typed, * Calculate the hyperbolic arcsecant of a value, * defined as `asech(x) = acosh(1/x) = ln(sqrt(1/x^2 - 1) + 1/x)`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix hyperbolic arcsecant, this function + * does not apply to matrices. * * Syntax: * @@ -24,8 +24,8 @@ export const createAsech = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * acsch, acoth * - * @param {number | Complex | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} Hyperbolic arcsecant of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} Hyperbolic arcsecant of x */ return typed(name, { number: function (x) { @@ -48,10 +48,6 @@ export const createAsech = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber: function (x) { return new BigNumber(1).div(x).acosh() - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) } }) }) diff --git a/src/function/trigonometry/asin.js b/src/function/trigonometry/asin.js index 5958096ace..ef26802880 100644 --- a/src/function/trigonometry/asin.js +++ b/src/function/trigonometry/asin.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' const name = 'asin' const dependencies = ['typed', 'config', 'Complex'] @@ -8,7 +7,8 @@ export const createAsin = /* #__PURE__ */ factory(name, dependencies, ({ typed, /** * Calculate the inverse sine of a value. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matric arcsine, this function does not apply + * to matrices. * * Syntax: * @@ -25,8 +25,8 @@ export const createAsin = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * sin, atan, acos * - * @param {number | BigNumber | Complex | Array | Matrix} x Function input - * @return {number | BigNumber | Complex | Array | Matrix} The arc sine of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} The arc sine of x */ return typed(name, { number: function (x) { @@ -43,11 +43,6 @@ export const createAsin = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber: function (x) { return x.asin() - }, - - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since asin(0) = 0 - return deepMap(x, this, true) } }) }) diff --git a/src/function/trigonometry/asinh.js b/src/function/trigonometry/asinh.js index 3cb41cc891..665a65deb7 100644 --- a/src/function/trigonometry/asinh.js +++ b/src/function/trigonometry/asinh.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { asinhNumber } from '../../plain/number/index.js' const name = 'asinh' @@ -10,7 +9,8 @@ export const createAsinh = /* #__PURE__ */ factory(name, dependencies, ({ typed * Calculate the hyperbolic arcsine of a value, * defined as `asinh(x) = ln(x + sqrt(x^2 + 1))`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix hyperbolic arcsine, this function + * does not apply to matrices. * * Syntax: * @@ -24,8 +24,8 @@ export const createAsinh = /* #__PURE__ */ factory(name, dependencies, ({ typed * * acosh, atanh * - * @param {number | Complex | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} Hyperbolic arcsine of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} Hyperbolic arcsine of x */ return typed('asinh', { number: asinhNumber, @@ -36,11 +36,6 @@ export const createAsinh = /* #__PURE__ */ factory(name, dependencies, ({ typed BigNumber: function (x) { return x.asinh() - }, - - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since asinh(0) = 0 - return deepMap(x, this, true) } }) }) diff --git a/src/function/trigonometry/atan.js b/src/function/trigonometry/atan.js index 27954b67a1..02ac3b6f9f 100644 --- a/src/function/trigonometry/atan.js +++ b/src/function/trigonometry/atan.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' const name = 'atan' const dependencies = ['typed'] @@ -8,7 +7,8 @@ export const createAtan = /* #__PURE__ */ factory(name, dependencies, ({ typed } /** * Calculate the inverse tangent of a value. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with matrix arctangent, this function does not apply + * to matrices. * * Syntax: * @@ -25,8 +25,8 @@ export const createAtan = /* #__PURE__ */ factory(name, dependencies, ({ typed } * * tan, asin, acos * - * @param {number | BigNumber | Complex | Array | Matrix} x Function input - * @return {number | BigNumber | Complex | Array | Matrix} The arc tangent of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} The arc tangent of x */ return typed('atan', { number: function (x) { @@ -39,11 +39,6 @@ export const createAtan = /* #__PURE__ */ factory(name, dependencies, ({ typed } BigNumber: function (x) { return x.atan() - }, - - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since atan(0) = 0 - return deepMap(x, this, true) } }) }) diff --git a/src/function/trigonometry/atan2.js b/src/function/trigonometry/atan2.js index 3ab817d882..cdc0794f77 100644 --- a/src/function/trigonometry/atan2.js +++ b/src/function/trigonometry/atan2.js @@ -1,11 +1,10 @@ import { factory } from '../../utils/factory.js' -import { createAlgorithm02 } from '../../type/matrix/utils/algorithm02.js' -import { createAlgorithm03 } from '../../type/matrix/utils/algorithm03.js' -import { createAlgorithm09 } from '../../type/matrix/utils/algorithm09.js' -import { createAlgorithm11 } from '../../type/matrix/utils/algorithm11.js' -import { createAlgorithm12 } from '../../type/matrix/utils/algorithm12.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatAlgo02xDS0 } from '../../type/matrix/utils/matAlgo02xDS0.js' +import { createMatAlgo03xDSf } from '../../type/matrix/utils/matAlgo03xDSf.js' +import { createMatAlgo09xS0Sf } from '../../type/matrix/utils/matAlgo09xS0Sf.js' +import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' +import { createMatAlgo12xSfs } from '../../type/matrix/utils/matAlgo12xSfs.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'atan2' const dependencies = [ @@ -17,13 +16,12 @@ const dependencies = [ ] export const createAtan2 = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, BigNumber, DenseMatrix }) => { - const algorithm02 = createAlgorithm02({ typed, equalScalar }) - const algorithm03 = createAlgorithm03({ typed }) - const algorithm09 = createAlgorithm09({ typed, equalScalar }) - const algorithm11 = createAlgorithm11({ typed, equalScalar }) - const algorithm12 = createAlgorithm12({ typed, DenseMatrix }) - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) + const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) + const matAlgo09xS0Sf = createMatAlgo09xS0Sf({ typed, equalScalar }) + const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) + const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Calculate the inverse tangent function with two arguments, y/x. @@ -54,71 +52,25 @@ export const createAtan2 = /* #__PURE__ */ factory(name, dependencies, ({ typed, * @param {number | Array | Matrix} x First dimension * @return {number | Array | Matrix} Four-quadrant inverse tangent */ - return typed(name, { + return typed( + name, + { + 'number, number': Math.atan2, - 'number, number': Math.atan2, + // Complex numbers doesn't seem to have a reasonable implementation of + // atan2(). Even Matlab removed the support, after they only calculated + // the atan only on base of the real part of the numbers and ignored + // the imaginary. - // Complex numbers doesn't seem to have a reasonable implementation of - // atan2(). Even Matlab removed the support, after they only calculated - // the atan only on base of the real part of the numbers and ignored the imaginary. - - 'BigNumber, BigNumber': function (y, x) { - return BigNumber.atan2(y, x) - }, - - 'SparseMatrix, SparseMatrix': function (x, y) { - return algorithm09(x, y, this, false) - }, - - 'SparseMatrix, DenseMatrix': function (x, y) { - // mind the order of y and x! - return algorithm02(y, x, this, true) - }, - - 'DenseMatrix, SparseMatrix': function (x, y) { - return algorithm03(x, y, this, false) - }, - - 'DenseMatrix, DenseMatrix': function (x, y) { - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - return this(matrix(x), y) + 'BigNumber, BigNumber': (y, x) => BigNumber.atan2(y, x) }, - - 'Matrix, Array': function (x, y) { - return this(x, matrix(y)) - }, - - 'SparseMatrix, number | BigNumber': function (x, y) { - return algorithm11(x, y, this, false) - }, - - 'DenseMatrix, number | BigNumber': function (x, y) { - return algorithm14(x, y, this, false) - }, - - 'number | BigNumber, SparseMatrix': function (x, y) { - // mind the order of y and x - return algorithm12(y, x, this, true) - }, - - 'number | BigNumber, DenseMatrix': function (x, y) { - // mind the order of y and x - return algorithm14(y, x, this, true) - }, - - 'Array, number | BigNumber': function (x, y) { - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'number | BigNumber, Array': function (x, y) { - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + matrixAlgorithmSuite({ + scalar: 'number | BigNumber', + SS: matAlgo09xS0Sf, + DS: matAlgo03xDSf, + SD: matAlgo02xDS0, + Ss: matAlgo11xS0s, + sS: matAlgo12xSfs + }) + ) }) diff --git a/src/function/trigonometry/atanh.js b/src/function/trigonometry/atanh.js index b4b9c4cfee..f9eda776f1 100644 --- a/src/function/trigonometry/atanh.js +++ b/src/function/trigonometry/atanh.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { atanhNumber } from '../../plain/number/index.js' const name = 'atanh' @@ -10,7 +9,8 @@ export const createAtanh = /* #__PURE__ */ factory(name, dependencies, ({ typed, * Calculate the hyperbolic arctangent of a value, * defined as `atanh(x) = ln((1 + x)/(1 - x)) / 2`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix hyperbolic arctangent, this function + * does not apply to matrices. * * Syntax: * @@ -24,8 +24,8 @@ export const createAtanh = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * acosh, asinh * - * @param {number | Complex | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} Hyperbolic arctangent of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} Hyperbolic arctangent of x */ return typed(name, { number: function (x) { @@ -41,11 +41,6 @@ export const createAtanh = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber: function (x) { return x.atanh() - }, - - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since atanh(0) = 0 - return deepMap(x, this, true) } }) }) diff --git a/src/function/trigonometry/cos.js b/src/function/trigonometry/cos.js index 4f30a5c2a0..3bf099ad57 100644 --- a/src/function/trigonometry/cos.js +++ b/src/function/trigonometry/cos.js @@ -1,14 +1,17 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' +import { createTrigUnit } from './trigUnit.js' const name = 'cos' const dependencies = ['typed'] export const createCos = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { + const trigUnit = createTrigUnit({ typed }) + /** * Calculate the cosine of a value. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix cosine, this function does not + * apply to matrices. * * Syntax: * @@ -28,29 +31,11 @@ export const createCos = /* #__PURE__ */ factory(name, dependencies, ({ typed }) * * cos, tan * - * @param {number | BigNumber | Complex | Unit | Array | Matrix} x Function input - * @return {number | BigNumber | Complex | Array | Matrix} Cosine of x + * @param {number | BigNumber | Complex | Unit} x Function input + * @return {number | BigNumber | Complex} Cosine of x */ return typed(name, { number: Math.cos, - - Complex: function (x) { - return x.cos() - }, - - BigNumber: function (x) { - return x.cos() - }, - - Unit: function (x) { - if (!x.hasBase(x.constructor.BASE_UNITS.ANGLE)) { - throw new TypeError('Unit in function cos is no angle') - } - return this(x.value) - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) - } - }) + 'Complex | BigNumber': x => x.cos() + }, trigUnit) }) diff --git a/src/function/trigonometry/cosh.js b/src/function/trigonometry/cosh.js index 3bcd921b93..7756f363f1 100644 --- a/src/function/trigonometry/cosh.js +++ b/src/function/trigonometry/cosh.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { cosh as coshNumber } from '../../utils/number.js' const name = 'cosh' @@ -10,7 +9,8 @@ export const createCosh = /* #__PURE__ */ factory(name, dependencies, ({ typed } * Calculate the hyperbolic cosine of a value, * defined as `cosh(x) = 1/2 * (exp(x) + exp(-x))`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix hyperbolic cosine, this function does + * not apply to matrices. * * Syntax: * @@ -24,29 +24,11 @@ export const createCosh = /* #__PURE__ */ factory(name, dependencies, ({ typed } * * sinh, tanh * - * @param {number | BigNumber | Complex | Unit | Array | Matrix} x Function input - * @return {number | BigNumber | Complex | Array | Matrix} Hyperbolic cosine of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} Hyperbolic cosine of x */ return typed(name, { number: coshNumber, - - Complex: function (x) { - return x.cosh() - }, - - BigNumber: function (x) { - return x.cosh() - }, - - Unit: function (x) { - if (!x.hasBase(x.constructor.BASE_UNITS.ANGLE)) { - throw new TypeError('Unit in function cosh is no angle') - } - return this(x.value) - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Complex | BigNumber': x => x.cosh() }) }) diff --git a/src/function/trigonometry/cot.js b/src/function/trigonometry/cot.js index 64f544a953..1e25951f97 100644 --- a/src/function/trigonometry/cot.js +++ b/src/function/trigonometry/cot.js @@ -1,15 +1,18 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { cotNumber } from '../../plain/number/index.js' +import { createTrigUnit } from './trigUnit.js' const name = 'cot' const dependencies = ['typed', 'BigNumber'] export const createCot = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber }) => { + const trigUnit = createTrigUnit({ typed }) + /** * Calculate the cotangent of a value. Defined as `cot(x) = 1 / tan(x)`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix cotangent, this function does not + * apply to matrices. * * Syntax: * @@ -29,24 +32,7 @@ export const createCot = /* #__PURE__ */ factory(name, dependencies, ({ typed, B */ return typed(name, { number: cotNumber, - - Complex: function (x) { - return x.cot() - }, - - BigNumber: function (x) { - return new BigNumber(1).div(x.tan()) - }, - - Unit: function (x) { - if (!x.hasBase(x.constructor.BASE_UNITS.ANGLE)) { - throw new TypeError('Unit in function cot is no angle') - } - return this(x.value) - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) - } - }) + Complex: x => x.cot(), + BigNumber: x => new BigNumber(1).div(x.tan()) + }, trigUnit) }) diff --git a/src/function/trigonometry/coth.js b/src/function/trigonometry/coth.js index 68b9b9bc3a..01a29f2681 100644 --- a/src/function/trigonometry/coth.js +++ b/src/function/trigonometry/coth.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { cothNumber } from '../../plain/number/index.js' const name = 'coth' @@ -10,7 +9,8 @@ export const createCoth = /* #__PURE__ */ factory(name, dependencies, ({ typed, * Calculate the hyperbolic cotangent of a value, * defined as `coth(x) = 1 / tanh(x)`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix hyperbolic cotangent, this function + * does not apply to matrices. * * Syntax: * @@ -26,29 +26,12 @@ export const createCoth = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * sinh, tanh, cosh * - * @param {number | Complex | Unit | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} Hyperbolic cotangent of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} Hyperbolic cotangent of x */ return typed(name, { number: cothNumber, - - Complex: function (x) { - return x.coth() - }, - - BigNumber: function (x) { - return new BigNumber(1).div(x.tanh()) - }, - - Unit: function (x) { - if (!x.hasBase(x.constructor.BASE_UNITS.ANGLE)) { - throw new TypeError('Unit in function coth is no angle') - } - return this(x.value) - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + Complex: x => x.coth(), + BigNumber: x => new BigNumber(1).div(x.tanh()) }) }) diff --git a/src/function/trigonometry/csc.js b/src/function/trigonometry/csc.js index b29da96870..5c84ab6266 100644 --- a/src/function/trigonometry/csc.js +++ b/src/function/trigonometry/csc.js @@ -1,15 +1,18 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { cscNumber } from '../../plain/number/index.js' +import { createTrigUnit } from './trigUnit.js' const name = 'csc' const dependencies = ['typed', 'BigNumber'] export const createCsc = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber }) => { + const trigUnit = createTrigUnit({ typed }) + /** * Calculate the cosecant of a value, defined as `csc(x) = 1/sin(x)`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix cosecant, this function does not + * apply to matrices. * * Syntax: * @@ -24,29 +27,12 @@ export const createCsc = /* #__PURE__ */ factory(name, dependencies, ({ typed, B * * sin, sec, cot * - * @param {number | Complex | Unit | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} Cosecant of x + * @param {number | BigNumber | Complex | Unit} x Function input + * @return {number | BigNumber | Complex} Cosecant of x */ return typed(name, { number: cscNumber, - - Complex: function (x) { - return x.csc() - }, - - BigNumber: function (x) { - return new BigNumber(1).div(x.sin()) - }, - - Unit: function (x) { - if (!x.hasBase(x.constructor.BASE_UNITS.ANGLE)) { - throw new TypeError('Unit in function csc is no angle') - } - return this(x.value) - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) - } - }) + Complex: x => x.csc(), + BigNumber: x => new BigNumber(1).div(x.sin()) + }, trigUnit) }) diff --git a/src/function/trigonometry/csch.js b/src/function/trigonometry/csch.js index f13c33faf6..e214247acb 100644 --- a/src/function/trigonometry/csch.js +++ b/src/function/trigonometry/csch.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { cschNumber } from '../../plain/number/index.js' const name = 'csch' @@ -10,7 +9,8 @@ export const createCsch = /* #__PURE__ */ factory(name, dependencies, ({ typed, * Calculate the hyperbolic cosecant of a value, * defined as `csch(x) = 1 / sinh(x)`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix hyperbolic cosecant, this function + * does not apply to matrices. * * Syntax: * @@ -26,29 +26,12 @@ export const createCsch = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * sinh, sech, coth * - * @param {number | Complex | Unit | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} Hyperbolic cosecant of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} Hyperbolic cosecant of x */ return typed(name, { number: cschNumber, - - Complex: function (x) { - return x.csch() - }, - - BigNumber: function (x) { - return new BigNumber(1).div(x.sinh()) - }, - - Unit: function (x) { - if (!x.hasBase(x.constructor.BASE_UNITS.ANGLE)) { - throw new TypeError('Unit in function csch is no angle') - } - return this(x.value) - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + Complex: x => x.csch(), + BigNumber: x => new BigNumber(1).div(x.sinh()) }) }) diff --git a/src/function/trigonometry/sec.js b/src/function/trigonometry/sec.js index d501854574..a875df0fd6 100644 --- a/src/function/trigonometry/sec.js +++ b/src/function/trigonometry/sec.js @@ -1,15 +1,18 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { secNumber } from '../../plain/number/index.js' +import { createTrigUnit } from './trigUnit.js' const name = 'sec' const dependencies = ['typed', 'BigNumber'] export const createSec = /* #__PURE__ */ factory(name, dependencies, ({ typed, BigNumber }) => { + const trigUnit = createTrigUnit({ typed }) + /** * Calculate the secant of a value, defined as `sec(x) = 1/cos(x)`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix secant, this function does not + * apply to matrices. * * Syntax: * @@ -24,29 +27,12 @@ export const createSec = /* #__PURE__ */ factory(name, dependencies, ({ typed, B * * cos, csc, cot * - * @param {number | Complex | Unit | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} Secant of x + * @param {number | BigNumber | Complex | Unit} x Function input + * @return {number | BigNumber | Complex} Secant of x */ return typed(name, { number: secNumber, - - Complex: function (x) { - return x.sec() - }, - - BigNumber: function (x) { - return new BigNumber(1).div(x.cos()) - }, - - Unit: function (x) { - if (!x.hasBase(x.constructor.BASE_UNITS.ANGLE)) { - throw new TypeError('Unit in function sec is no angle') - } - return this(x.value) - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) - } - }) + Complex: x => x.sec(), + BigNumber: x => new BigNumber(1).div(x.cos()) + }, trigUnit) }) diff --git a/src/function/trigonometry/sech.js b/src/function/trigonometry/sech.js index 49f4105566..2873e1a3cd 100644 --- a/src/function/trigonometry/sech.js +++ b/src/function/trigonometry/sech.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { sechNumber } from '../../plain/number/index.js' const name = 'sech' @@ -10,7 +9,8 @@ export const createSech = /* #__PURE__ */ factory(name, dependencies, ({ typed, * Calculate the hyperbolic secant of a value, * defined as `sech(x) = 1 / cosh(x)`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix hyperbolic secant, this function does + * not apply to matrices. * * Syntax: * @@ -26,29 +26,12 @@ export const createSech = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * cosh, csch, coth * - * @param {number | Complex | Unit | Array | Matrix} x Function input - * @return {number | Complex | Array | Matrix} Hyperbolic secant of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} Hyperbolic secant of x */ return typed(name, { number: sechNumber, - - Complex: function (x) { - return x.sech() - }, - - BigNumber: function (x) { - return new BigNumber(1).div(x.cosh()) - }, - - Unit: function (x) { - if (!x.hasBase(x.constructor.BASE_UNITS.ANGLE)) { - throw new TypeError('Unit in function sech is no angle') - } - return this(x.value) - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + Complex: x => x.sech(), + BigNumber: x => new BigNumber(1).div(x.cosh()) }) }) diff --git a/src/function/trigonometry/sin.js b/src/function/trigonometry/sin.js index a4efde3f8c..a5d5b9a08c 100644 --- a/src/function/trigonometry/sin.js +++ b/src/function/trigonometry/sin.js @@ -1,14 +1,17 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' +import { createTrigUnit } from './trigUnit.js' const name = 'sin' const dependencies = ['typed'] export const createSin = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { + const trigUnit = createTrigUnit({ typed }) + /** * Calculate the sine of a value. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix sine, this function does not apply + * to matrices. * * Syntax: * @@ -28,30 +31,11 @@ export const createSin = /* #__PURE__ */ factory(name, dependencies, ({ typed }) * * cos, tan * - * @param {number | BigNumber | Complex | Unit | Array | Matrix} x Function input - * @return {number | BigNumber | Complex | Array | Matrix} Sine of x + * @param {number | BigNumber | Complex | Unit} x Function input + * @return {number | BigNumber | Complex} Sine of x */ return typed(name, { number: Math.sin, - - Complex: function (x) { - return x.sin() - }, - - BigNumber: function (x) { - return x.sin() - }, - - Unit: function (x) { - if (!x.hasBase(x.constructor.BASE_UNITS.ANGLE)) { - throw new TypeError('Unit in function sin is no angle') - } - return this(x.value) - }, - - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since sin(0) = 0 - return deepMap(x, this, true) - } - }) + 'Complex | BigNumber': x => x.sin() + }, trigUnit) }) diff --git a/src/function/trigonometry/sinh.js b/src/function/trigonometry/sinh.js index db2652f2eb..a5f3acb35a 100644 --- a/src/function/trigonometry/sinh.js +++ b/src/function/trigonometry/sinh.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { sinhNumber } from '../../plain/number/index.js' const name = 'sinh' @@ -10,7 +9,8 @@ export const createSinh = /* #__PURE__ */ factory(name, dependencies, ({ typed } * Calculate the hyperbolic sine of a value, * defined as `sinh(x) = 1/2 * (exp(x) - exp(-x))`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix hyperbolic sine, this function does + * not apply to matrices. * * Syntax: * @@ -24,30 +24,11 @@ export const createSinh = /* #__PURE__ */ factory(name, dependencies, ({ typed } * * cosh, tanh * - * @param {number | BigNumber | Complex | Unit | Array | Matrix} x Function input - * @return {number | BigNumber | Complex | Array | Matrix} Hyperbolic sine of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} Hyperbolic sine of x */ return typed(name, { number: sinhNumber, - - Complex: function (x) { - return x.sinh() - }, - - BigNumber: function (x) { - return x.sinh() - }, - - Unit: function (x) { - if (!x.hasBase(x.constructor.BASE_UNITS.ANGLE)) { - throw new TypeError('Unit in function sinh is no angle') - } - return this(x.value) - }, - - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since sinh(0) = 0 - return deepMap(x, this, true) - } + 'Complex | BigNumber': x => x.sinh() }) }) diff --git a/src/function/trigonometry/tan.js b/src/function/trigonometry/tan.js index aa1bc9e2d4..3078f243ef 100644 --- a/src/function/trigonometry/tan.js +++ b/src/function/trigonometry/tan.js @@ -1,14 +1,17 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' +import { createTrigUnit } from './trigUnit.js' const name = 'tan' const dependencies = ['typed'] export const createTan = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { + const trigUnit = createTrigUnit({ typed }) + /** * Calculate the tangent of a value. `tan(x)` is equal to `sin(x) / cos(x)`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with the matrix tangent, this function does not apply + * to matrices. * * Syntax: * @@ -25,30 +28,11 @@ export const createTan = /* #__PURE__ */ factory(name, dependencies, ({ typed }) * * atan, sin, cos * - * @param {number | BigNumber | Complex | Unit | Array | Matrix} x Function input - * @return {number | BigNumber | Complex | Array | Matrix} Tangent of x + * @param {number | BigNumber | Complex | Unit} x Function input + * @return {number | BigNumber | Complex} Tangent of x */ return typed(name, { number: Math.tan, - - Complex: function (x) { - return x.tan() - }, - - BigNumber: function (x) { - return x.tan() - }, - - Unit: function (x) { - if (!x.hasBase(x.constructor.BASE_UNITS.ANGLE)) { - throw new TypeError('Unit in function tan is no angle') - } - return this(x.value) - }, - - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since tan(0) = 0 - return deepMap(x, this, true) - } - }) + 'Complex | BigNumber': x => x.tan() + }, trigUnit) }) diff --git a/src/function/trigonometry/tanh.js b/src/function/trigonometry/tanh.js index 6b51a9e96c..8e886b0630 100644 --- a/src/function/trigonometry/tanh.js +++ b/src/function/trigonometry/tanh.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' -import { deepMap } from '../../utils/collection.js' import { tanh as _tanh } from '../../utils/number.js' const name = 'tanh' @@ -10,7 +9,8 @@ export const createTanh = /* #__PURE__ */ factory(name, dependencies, ({ typed } * Calculate the hyperbolic tangent of a value, * defined as `tanh(x) = (exp(2 * x) - 1) / (exp(2 * x) + 1)`. * - * For matrices, the function is evaluated element wise. + * To avoid confusion with matrix hyperbolic tangent, this function does + * not apply to matrices. * * Syntax: * @@ -27,30 +27,11 @@ export const createTanh = /* #__PURE__ */ factory(name, dependencies, ({ typed } * * sinh, cosh, coth * - * @param {number | BigNumber | Complex | Unit | Array | Matrix} x Function input - * @return {number | BigNumber | Complex | Array | Matrix} Hyperbolic tangent of x + * @param {number | BigNumber | Complex} x Function input + * @return {number | BigNumber | Complex} Hyperbolic tangent of x */ return typed('tanh', { number: _tanh, - - Complex: function (x) { - return x.tanh() - }, - - BigNumber: function (x) { - return x.tanh() - }, - - Unit: function (x) { - if (!x.hasBase(x.constructor.BASE_UNITS.ANGLE)) { - throw new TypeError('Unit in function tanh is no angle') - } - return this(x.value) - }, - - 'Array | Matrix': function (x) { - // deep map collection, skip zeros since tanh(0) = 0 - return deepMap(x, this, true) - } + 'Complex | BigNumber': x => x.tanh() }) }) diff --git a/src/function/trigonometry/trigUnit.js b/src/function/trigonometry/trigUnit.js new file mode 100644 index 0000000000..7d2ccc1f6d --- /dev/null +++ b/src/function/trigonometry/trigUnit.js @@ -0,0 +1,12 @@ +import { factory } from '../../utils/factory.js' + +export const createTrigUnit = /* #__PURE__ */ factory( + 'trigUnit', ['typed'], ({ typed }) => ({ + Unit: typed.referToSelf(self => x => { + if (!x.hasBase(x.constructor.BASE_UNITS.ANGLE)) { + throw new TypeError('Unit in function cot is no angle') + } + return typed.find(self, x.valueType())(x.value) + }) + }) +) diff --git a/src/function/unit/to.js b/src/function/unit/to.js index 6f30e78c47..9b59aad7c6 100644 --- a/src/function/unit/to.js +++ b/src/function/unit/to.js @@ -1,6 +1,5 @@ import { factory } from '../../utils/factory.js' -import { createAlgorithm13 } from '../../type/matrix/utils/algorithm13.js' -import { createAlgorithm14 } from '../../type/matrix/utils/algorithm14.js' +import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'to' const dependencies = [ @@ -9,8 +8,7 @@ const dependencies = [ ] export const createTo = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix }) => { - const algorithm13 = createAlgorithm13({ typed }) - const algorithm14 = createAlgorithm14({ typed }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) /** * Change the unit of a value. @@ -36,49 +34,9 @@ export const createTo = /* #__PURE__ */ factory(name, dependencies, ({ typed, ma * or a unit without value. * @return {Unit | Array | Matrix} value with changed, fixed unit. */ - return typed(name, { - 'Unit, Unit | string': function (x, unit) { - return x.to(unit) - }, - - 'Matrix, Matrix': function (x, y) { - // SparseMatrix does not support Units - return algorithm13(x, y, this) - }, - - 'Array, Array': function (x, y) { - // use matrix implementation - return this(matrix(x), matrix(y)).valueOf() - }, - - 'Array, Matrix': function (x, y) { - // use matrix implementation - return this(matrix(x), y) - }, - - 'Matrix, Array': function (x, y) { - // use matrix implementation - return this(x, matrix(y)) - }, - - 'Matrix, any': function (x, y) { - // SparseMatrix does not support Units - return algorithm14(x, y, this, false) - }, - - 'any, Matrix': function (x, y) { - // SparseMatrix does not support Units - return algorithm14(y, x, this, true) - }, - - 'Array, any': function (x, y) { - // use matrix implementation - return algorithm14(matrix(x), y, this, false).valueOf() - }, - - 'any, Array': function (x, y) { - // use matrix implementation - return algorithm14(matrix(y), x, this, true).valueOf() - } - }) + return typed( + name, + { 'Unit, Unit | string': (x, unit) => x.to(unit) }, + matrixAlgorithmSuite({ Ds: true }) + ) }) diff --git a/src/function/utils/hasNumericValue.js b/src/function/utils/hasNumericValue.js index c418aaaa79..57638cac77 100644 --- a/src/function/utils/hasNumericValue.js +++ b/src/function/utils/hasNumericValue.js @@ -22,6 +22,7 @@ export const createHasNumericValue = /* #__PURE__ */ factory(name, dependencies, * math.hasNumericValue(math.bignumber(500)) // returns true * math.hasNumericValue(math.fraction(4)) // returns true * math.hasNumericValue(math.complex('2-4i') // returns false + * math.hasNumericValue(false) // returns true * math.hasNumericValue([2.3, 'foo', false]) // returns [true, false, true] * * See also: @@ -34,6 +35,7 @@ export const createHasNumericValue = /* #__PURE__ */ factory(name, dependencies, * Throws an error in case of unknown types. */ return typed(name, { + boolean: () => true, string: function (x) { return x.trim().length > 0 && !isNaN(Number(x)) }, diff --git a/src/function/utils/isInteger.js b/src/function/utils/isInteger.js index c822936a22..6953a2d494 100644 --- a/src/function/utils/isInteger.js +++ b/src/function/utils/isInteger.js @@ -46,8 +46,6 @@ export const createIsInteger = /* #__PURE__ */ factory(name, dependencies, ({ ty return x.d === 1 && isFinite(x.n) }, - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/function/utils/isNegative.js b/src/function/utils/isNegative.js index 89a8925b7a..c48a0e4cb8 100644 --- a/src/function/utils/isNegative.js +++ b/src/function/utils/isNegative.js @@ -46,12 +46,9 @@ export const createIsNegative = /* #__PURE__ */ factory(name, dependencies, ({ t return x.s < 0 // It's enough to decide on the sign }, - Unit: function (x) { - return this(x.value) - }, + Unit: typed.referToSelf(self => + x => typed.find(self, x.valueType())(x.value)), - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/function/utils/isNumeric.js b/src/function/utils/isNumeric.js index fa93ae7647..d2c18e4400 100644 --- a/src/function/utils/isNumeric.js +++ b/src/function/utils/isNumeric.js @@ -35,16 +35,8 @@ export const createIsNumeric = /* #__PURE__ */ factory(name, dependencies, ({ ty * Throws an error in case of unknown types. */ return typed(name, { - 'number | BigNumber | Fraction | boolean': function () { - return true - }, - - 'Complex | Unit | string | null | undefined | Node': function () { - return false - }, - - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'number | BigNumber | Fraction | boolean': () => true, + 'Complex | Unit | string | null | undefined | Node': () => false, + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/function/utils/isPositive.js b/src/function/utils/isPositive.js index bb66dcdf88..6e2ee4024a 100644 --- a/src/function/utils/isPositive.js +++ b/src/function/utils/isPositive.js @@ -48,12 +48,9 @@ export const createIsPositive = /* #__PURE__ */ factory(name, dependencies, ({ t return x.s > 0 && x.n > 0 }, - Unit: function (x) { - return this(x.value) - }, + Unit: typed.referToSelf(self => + x => typed.find(self, x.valueType())(x.value)), - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/function/utils/isPrime.js b/src/function/utils/isPrime.js index f03bdddbf0..f04f853155 100644 --- a/src/function/utils/isPrime.js +++ b/src/function/utils/isPrime.js @@ -117,8 +117,6 @@ export const createIsPrime = /* #__PURE__ */ factory(name, dependencies, ({ type return true }, - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/function/utils/isZero.js b/src/function/utils/isZero.js index 1841c53984..3364e543fa 100644 --- a/src/function/utils/isZero.js +++ b/src/function/utils/isZero.js @@ -54,12 +54,9 @@ export const createIsZero = /* #__PURE__ */ factory(name, dependencies, ({ typed return x.d === 1 && x.n === 0 }, - Unit: function (x) { - return this(x.value) - }, + Unit: typed.referToSelf(self => + x => typed.find(self, x.valueType())(x.value)), - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/function/utils/numeric.js b/src/function/utils/numeric.js index f945dd9918..2cf29cb05d 100644 --- a/src/function/utils/numeric.js +++ b/src/function/utils/numeric.js @@ -33,12 +33,12 @@ export const createNumeric = /* #__PURE__ */ factory(name, dependencies, ({ numb * * Examples: * - * math.numeric('4') // returns number 4 - * math.numeric('4', 'number') // returns number 4 + * math.numeric('4') // returns 4 + * math.numeric('4', 'number') // returns 4 * math.numeric('4', 'BigNumber') // returns BigNumber 4 * math.numeric('4', 'Fraction') // returns Fraction 4 * math.numeric(4, 'Fraction') // returns Fraction 4 - * math.numeric(math.fraction(2, 5), 'number') // returns number 0.4 + * math.numeric(math.fraction(2, 5), 'number') // returns 0.4 * * See also: * @@ -52,7 +52,10 @@ export const createNumeric = /* #__PURE__ */ factory(name, dependencies, ({ numb * @return {number | BigNumber | Fraction} * Returns an instance of the numeric in the requested type */ - return function numeric (value, outputType) { + return function numeric (value, outputType = 'number', check) { + if (check !== undefined) { + throw new SyntaxError('numeric() takes one or two arguments') + } const inputType = typeOf(value) if (!(inputType in validInputTypes)) { diff --git a/src/function/utils/typeOf.js b/src/function/utils/typeOf.js index a5f434c646..8ca1f44489 100644 --- a/src/function/utils/typeOf.js +++ b/src/function/utils/typeOf.js @@ -6,46 +6,7 @@ const dependencies = ['typed'] export const createTypeOf = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { /** - * Determine the type of a variable. - * - * Function `typeOf` recognizes the following types of objects: - * - * Object | Returns | Example - * ---------------------- | ------------- | ------------------------------------------ - * null | `'null'` | `math.typeOf(null)` - * number | `'number'` | `math.typeOf(3.5)` - * boolean | `'boolean'` | `math.typeOf(true)` - * string | `'string'` | `math.typeOf('hello world')` - * Array | `'Array'` | `math.typeOf([1, 2, 3])` - * Date | `'Date'` | `math.typeOf(new Date())` - * Function | `'Function'` | `math.typeOf(function () {})` - * Object | `'Object'` | `math.typeOf({a: 2, b: 3})` - * RegExp | `'RegExp'` | `math.typeOf(/a regexp/)` - * undefined | `'undefined'` | `math.typeOf(undefined)` - * math.BigNumber | `'BigNumber'` | `math.typeOf(math.bignumber('2.3e500'))` - * math.Chain | `'Chain'` | `math.typeOf(math.chain(2))` - * math.Complex | `'Complex'` | `math.typeOf(math.complex(2, 3))` - * math.Fraction | `'Fraction'` | `math.typeOf(math.fraction(1, 3))` - * math.Help | `'Help'` | `math.typeOf(math.help('sqrt'))` - * math.Help | `'Help'` | `math.typeOf(math.help('sqrt'))` - * math.Index | `'Index'` | `math.typeOf(math.index(1, 3))` - * math.Matrix | `'Matrix'` | `math.typeOf(math.matrix([[1,2], [3, 4]]))` - * math.Range | `'Range'` | `math.typeOf(math.range(0, 10))` - * math.ResultSet | `'ResultSet'` | `math.typeOf(math.evaluate('a=2\nb=3'))` - * math.Unit | `'Unit'` | `math.typeOf(math.unit('45 deg'))` - * math.AccessorNode | `'AccessorNode'` | `math.typeOf(math.parse('A[2]'))` - * math.ArrayNode | `'ArrayNode'` | `math.typeOf(math.parse('[1,2,3]'))` - * math.AssignmentNode | `'AssignmentNode'` | `math.typeOf(math.parse('x=2'))` - * math.BlockNode | `'BlockNode'` | `math.typeOf(math.parse('a=2; b=3'))` - * math.ConditionalNode | `'ConditionalNode'` | `math.typeOf(math.parse('x<0 ? -x : x'))` - * math.ConstantNode | `'ConstantNode'` | `math.typeOf(math.parse('2.3'))` - * math.FunctionAssignmentNode | `'FunctionAssignmentNode'` | `math.typeOf(math.parse('f(x)=x^2'))` - * math.FunctionNode | `'FunctionNode'` | `math.typeOf(math.parse('sqrt(4)'))` - * math.IndexNode | `'IndexNode'` | `math.typeOf(math.parse('A[2]').index)` - * math.ObjectNode | `'ObjectNode'` | `math.typeOf(math.parse('{a:2}'))` - * math.ParenthesisNode | `'ParenthesisNode'` | `math.typeOf(math.parse('(2+3)'))` - * math.RangeNode | `'RangeNode'` | `math.typeOf(math.parse('1:10'))` - * math.SymbolNode | `'SymbolNode'` | `math.typeOf(math.parse('x'))` + * Determine the type of an entity. * * Syntax: * @@ -53,10 +14,43 @@ export const createTypeOf = /* #__PURE__ */ factory(name, dependencies, ({ typed * * Examples: * - * math.typeOf(3.5) // returns 'number' - * math.typeOf(math.complex('2-4i')) // returns 'Complex' - * math.typeOf(math.unit('45 deg')) // returns 'Unit' - * math.typeOf('hello world') // returns 'string' + * // This list is intended to include all relevant types, for testing + * // purposes: + * math.typeOf(3.5) // returns 'number' + * math.typeOf(math.complex('2-4i')) // returns 'Complex' + * math.typeOf(math.unit('45 deg')) // returns 'Unit' + * math.typeOf('hello world') // returns 'string' + * math.typeOf(null) // returns 'null' + * math.typeOf(true) // returns 'boolean' + * math.typeOf([1, 2, 3]) // returns 'Array' + * math.typeOf(new Date()) // returns 'Date' + * math.typeOf(function () {}) // returns 'function' + * math.typeOf({a: 2, b: 3}) // returns 'Object' + * math.typeOf(/a regexp/) // returns 'RegExp' + * math.typeOf(undefined) // returns 'undefined' + * math.typeOf(math.bignumber('23e99')) // returns 'BigNumber' + * math.typeOf(math.chain(2)) // returns 'Chain' + * math.typeOf(math.fraction(1, 3)) // returns 'Fraction' + * math.typeOf(math.help('sqrt')) // returns 'Help' + * math.typeOf(math.index(1, 3)) // returns 'Index' + * math.typeOf(math.matrix([[1],[3]])) // returns 'DenseMatrix' + * math.typeOf(math.matrix([],'sparse')) // returns 'SparseMatrix' + * math.typeOf(new math.Range(0, 10)) // returns 'Range' + * math.typeOf(math.evaluate('a=2\na')) // returns 'ResultSet' + * math.typeOf(math.parse('A[2]')) // returns 'AccessorNode' + * math.typeOf(math.parse('[1,2,3]')) // returns 'ArrayNode' + * math.typeOf(math.parse('x=2')) // returns 'AssignmentNode' + * math.typeOf(math.parse('a=2; b=3')) // returns 'BlockNode' + * math.typeOf(math.parse('x<0?-1:1')) // returns 'ConditionalNode' + * math.typeOf(math.parse('2.3')) // returns 'ConstantNode' + * math.typeOf(math.parse('f(x)=x^2')) // returns 'FunctionAssignmentNode' + * math.typeOf(math.parse('sqrt(4)')) // returns 'FunctionNode' + * math.typeOf(math.parse('A[2]').index) // returns 'IndexNode' + * math.typeOf(math.parse('{a:2}')) // returns 'ObjectNode' + * math.typeOf(math.parse('(2+3)')) // returns 'ParenthesisNode' + * math.typeOf(math.parse('1:10')) // returns 'RangeNode' + * math.typeOf(math.parse('a x => deepMap(x, self)) }) }) diff --git a/src/type/boolean.js b/src/type/boolean.js index 09a52d8190..7ac890c994 100644 --- a/src/type/boolean.js +++ b/src/type/boolean.js @@ -71,8 +71,6 @@ export const createBoolean = /* #__PURE__ */ factory(name, dependencies, ({ type throw new Error('Cannot convert "' + x + '" to a boolean') }, - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/type/chain/Chain.js b/src/type/chain/Chain.js index 351252a4a5..31ae3e9c7d 100644 --- a/src/type/chain/Chain.js +++ b/src/type/chain/Chain.js @@ -4,9 +4,9 @@ import { hasOwnProperty, lazy } from '../../utils/object.js' import { factory } from '../../utils/factory.js' const name = 'Chain' -const dependencies = ['?on', 'math'] +const dependencies = ['?on', 'math', 'typed'] -export const createChainClass = /* #__PURE__ */ factory(name, dependencies, ({ on, math }) => { +export const createChainClass = /* #__PURE__ */ factory(name, dependencies, ({ on, math, typed }) => { /** * @constructor Chain * Wrap any value in a chain, allowing to perform chained operations on @@ -130,11 +130,26 @@ export const createChainClass = /* #__PURE__ */ factory(name, dependencies, ({ o */ function chainify (fn) { return function () { - const args = [this.value] // `this` will be the context of a Chain instance + // Here, `this` will be the context of a Chain instance + if (arguments.length === 0) { + return new Chain(fn(this.value)) + } + const args = [this.value] for (let i = 0; i < arguments.length; i++) { args[i + 1] = arguments[i] } - + if (typed.isTypedFunction(fn)) { + const sigObject = typed.resolve(fn, args) + // We want to detect if a rest parameter has matched across the + // value in the chain and the current arguments of this call. + // That is the case if and only if the matching signature has + // exactly one parameter (which then must be a rest parameter + // as it is matching at least two actual arguments). + if (sigObject.params.length === 1) { + throw new Error('chain function ' + fn.name + ' cannot match rest parameter between chain value and additional arguments.') + } + return new Chain(sigObject.implementation.apply(fn, args)) + } return new Chain(fn.apply(fn, args)) } } diff --git a/src/type/complex/Complex.js b/src/type/complex/Complex.js index 5953c216ab..1078e5ae6c 100644 --- a/src/type/complex/Complex.js +++ b/src/type/complex/Complex.js @@ -10,6 +10,8 @@ export const createComplexClass = /* #__PURE__ */ factory(name, dependencies, () /** * Attach type information */ + Object.defineProperty(Complex, 'name', { value: 'Complex' }) + Complex.prototype.constructor = Complex Complex.prototype.type = 'Complex' Complex.prototype.isComplex = true diff --git a/src/type/complex/function/complex.js b/src/type/complex/function/complex.js index f3e8122f31..b94327e2b4 100644 --- a/src/type/complex/function/complex.js +++ b/src/type/complex/function/complex.js @@ -89,8 +89,6 @@ export const createComplex = /* #__PURE__ */ factory(name, dependencies, ({ type throw new Error('Expected object with properties (re and im) or (r and phi) or (abs and arg)') }, - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/type/fraction/Fraction.js b/src/type/fraction/Fraction.js index 2fc580c639..be84135d1c 100644 --- a/src/type/fraction/Fraction.js +++ b/src/type/fraction/Fraction.js @@ -8,6 +8,8 @@ export const createFractionClass = /* #__PURE__ */ factory(name, dependencies, ( /** * Attach type information */ + Object.defineProperty(Fraction, 'name', { value: 'Fraction' }) + Fraction.prototype.constructor = Fraction Fraction.prototype.type = 'Fraction' Fraction.prototype.isFraction = true diff --git a/src/type/fraction/function/fraction.js b/src/type/fraction/function/fraction.js index c13491f54c..39fde6c34d 100644 --- a/src/type/fraction/function/fraction.js +++ b/src/type/fraction/function/fraction.js @@ -75,8 +75,6 @@ export const createFraction = /* #__PURE__ */ factory(name, dependencies, ({ typ return new Fraction(x) }, - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/type/matrix/DenseMatrix.js b/src/type/matrix/DenseMatrix.js index 80fb03abe2..a16d037e3c 100644 --- a/src/type/matrix/DenseMatrix.js +++ b/src/type/matrix/DenseMatrix.js @@ -5,6 +5,7 @@ import { isInteger } from '../../utils/number.js' import { clone, deepStrictEqual } from '../../utils/object.js' import { DimensionError } from '../../error/DimensionError.js' import { factory } from '../../utils/factory.js' +import { maxArgumentCount } from '../../utils/function.js' const name = 'DenseMatrix' const dependencies = [ @@ -73,6 +74,8 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies /** * Attach type information */ + Object.defineProperty(DenseMatrix, 'name', { value: 'DenseMatrix' }) + DenseMatrix.prototype.constructor = DenseMatrix DenseMatrix.prototype.type = 'DenseMatrix' DenseMatrix.prototype.isDenseMatrix = true @@ -535,13 +538,21 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies DenseMatrix.prototype.map = function (callback) { // matrix instance const me = this + const args = maxArgumentCount(callback) const recurse = function (value, index) { if (isArray(value)) { return value.map(function (child, i) { return recurse(child, index.concat(i)) }) } else { - return callback(value, index, me) + // invoke the callback function with the right number of arguments + if (args === 1) { + return callback(value) + } else if (args === 2) { + return callback(value, index) + } else { // 3 or -1 + return callback(value, index, me) + } } } diff --git a/src/type/matrix/SparseMatrix.js b/src/type/matrix/SparseMatrix.js index 0a9ed203eb..f06ffdc8d5 100644 --- a/src/type/matrix/SparseMatrix.js +++ b/src/type/matrix/SparseMatrix.js @@ -5,6 +5,7 @@ import { clone, deepStrictEqual } from '../../utils/object.js' import { arraySize, getArrayDataType, processSizesWildcard, unsqueeze, validateIndex } from '../../utils/array.js' import { factory } from '../../utils/factory.js' import { DimensionError } from '../../error/DimensionError.js' +import { maxArgumentCount } from '../../utils/function.js' const name = 'SparseMatrix' const dependencies = [ @@ -149,6 +150,8 @@ export const createSparseMatrixClass = /* #__PURE__ */ factory(name, dependencie /** * Attach type information */ + Object.defineProperty(SparseMatrix, 'name', { value: 'SparseMatrix' }) + SparseMatrix.prototype.constructor = SparseMatrix SparseMatrix.prototype.type = 'SparseMatrix' SparseMatrix.prototype.isSparseMatrix = true @@ -849,8 +852,11 @@ export const createSparseMatrixClass = /* #__PURE__ */ factory(name, dependencie const rows = this._size[0] const columns = this._size[1] // invoke callback + const args = maxArgumentCount(callback) const invoke = function (v, i, j) { // invoke callback + if (args === 1) return callback(v) + if (args === 2) return callback(v, [i, j]) return callback(v, [i, j], me) } // invoke _map diff --git a/src/type/matrix/utils/README.md b/src/type/matrix/utils/README.md index 3ffbe555b1..622d7cb7f6 100644 --- a/src/type/matrix/utils/README.md +++ b/src/type/matrix/utils/README.md @@ -1,6 +1,11 @@ ###Algorithms for the implementation of element wise operations between a Dense and Sparse matrices: -- **Algorithm 1 `x(dense, sparse)`** +Note that brief mnemonics for the algorithm's operations are given after their +numbers, and are included in the JavaScript as a suffix, with a separator `x` +between the number and the suffix. (Linting rules would not alow `_` as the +separator, unfortunately.) + +- **Algorithm 01 (DSid) `x(dense, sparse)`** * Algorithm should clone `DenseMatrix` and call the `x(d(i,j), s(i,j))` operation for the items in the Dense and Sparse matrices (iterating on the Sparse matrix nonzero items), updating the cloned matrix. * Output type is a `DenseMatrix` (the cloned matrix) * `x()` operation invoked NZ times (number of nonzero items in `SparseMatrix`) @@ -10,7 +15,7 @@ Cij = Dij ; otherwise ```` -- **Algorithm 2 `x(dense, sparse)`** +- **Algorithm 02 (DS0) `x(dense, sparse)`** * Algorithm should iterate `SparseMatrix` (nonzero items) and call the `x(d(i,j),s(i,j))` operation for the items in the Sparse and Dense matrices (since zero & X == zero) * Output type is a `SparseMatrix` since the number of nonzero items will be less or equal the number of nonzero elements in the Sparse Matrix. * `x()` operation invoked NZ times (number of nonzero items in `SparseMatrix`) @@ -20,7 +25,7 @@ Cij = 0 ; otherwise ```` -- **Algorithm 3 `x(dense, sparse)`** +- **Algorithm 03 (DSf) `x(dense, sparse)`** * Algorithm should iterate `SparseMatrix` (nonzero and zero items) and call the `x(s(i,j),d(i,j))` operation for the items in the Dense and Sparse matrices * Output type is a `DenseMatrix` * `x()` operation invoked M*N times @@ -30,7 +35,7 @@ Cij = x(Dij, 0) ; otherwise ```` -- **Algorithm 4 `x(sparse, sparse)`** +- **Algorithm 04 (SidSid) `x(sparse, sparse)`** * Algorithm should iterate on the nonzero values of matrices A and B and call `x(Aij, Bij)` when both matrices contain value at (i,j) * Output type is a `SparseMatrix` * `x()` operation invoked NZ times (number of nonzero items at the same (i,j) for both matrices) @@ -43,7 +48,7 @@ ###Algorithms for the implementation of element wise operations between a Sparse matrices: -- **Algorithm 5 `x(sparse, sparse)`** +- **Algorithm 05 (SfSf) `x(sparse, sparse)`** * Algorithm should iterate on the nonzero values of matrices A and B and call `x(Aij, Bij)` for every nonzero value. * Output type is a `SparseMatrix` * `x()` operation invoked NZ times (number of nonzero values in A only + number of nonzero values in B only + number of nonzero values in A and B) @@ -53,7 +58,7 @@ Cij = 0 ; otherwise ```` -- **Algorithm 6 `x(sparse, sparse)`** +- **Algorithm 06 (S0S0) `x(sparse, sparse)`** * Algorithm should iterate on the nonzero values of matrices A and B and call `x(Aij, Bij)` when both matrices contain value at (i,j). * Output type is a `SparseMatrix` * `x()` operation invoked NZ times (number of nonzero items at the same (i,j) for both matrices) @@ -63,7 +68,7 @@ Cij = 0 ; otherwise ```` -- **Algorithm 7 `x(sparse, sparse)`** +- **Algorithm 07 (SSf) `x(sparse, sparse)`** * Algorithm should iterate on all values of matrices A and B and call `x(Aij, Bij)` * Output type is a `DenseMatrix` * `x()` operation invoked MxN times @@ -72,7 +77,7 @@ Cij = x(Aij, Bij); ```` -- **Algorithm 8 `x(sparse, sparse)`** +- **Algorithm 08 (S0Sid) `x(sparse, sparse)`** * Algorithm should iterate on the nonzero values of matrices A and B and call `x(Aij, Bij)` when both matrices contain value at (i,j). Use the value from Aij when Bij is zero. * Output type is a `SparseMatrix` * `x()` operation invoked NZ times (number of nonzero items at the same (i,j) for both matrices) @@ -83,7 +88,7 @@ Cij = 0 ; otherwise ```` -- **Algorithm 9 `x(sparse, sparse)`** +- **Algorithm 9 (S0Sf) `x(sparse, sparse)`** * Algorithm should iterate on the nonzero values of matrices A `x(Aij, Bij)`. * Output type is a `SparseMatrix` * `x()` operation invoked NZA times (number of nonzero items in A) @@ -95,7 +100,7 @@ ###Algorithms for the implementation of element wise operations between a Sparse and Scalar Value: -- **Algorithm 10 `x(sparse, scalar)`** +- **Algorithm 10 (Sids) `x(sparse, scalar)`** * Algorithm should iterate on the nonzero values of matrix A and call `x(Aij, N)`. * Output type is a `DenseMatrix` * `x()` operation invoked NZ times (number of nonzero items) @@ -105,7 +110,7 @@ Cij = N ; otherwise ```` -- **Algorithm 11 `x(sparse, scalar)`** +- **Algorithm 11 (S0s) `x(sparse, scalar)`** * Algorithm should iterate on the nonzero values of matrix A and call `x(Aij, N)`. * Output type is a `SparseMatrix` * `x()` operation invoked NZ times (number of nonzero items) @@ -115,7 +120,7 @@ Cij = 0 ; otherwise** ```` -- **Algorithm 12 `x(sparse, scalar)`** +- **Algorithm 12 (Sfs) `x(sparse, scalar)`** * Algorithm should iterate on the zero and nonzero values of matrix A and call `x(Aij, N)`. * Output type is a `DenseMatrix` * `x()` operation invoked MxN times. @@ -127,7 +132,7 @@ ###Algorithms for the implementation of element wise operations between a Dense and Dense matrices: -- **Algorithm 13 `x(dense, dense)` +- **Algorithm 13 (DD) `x(dense, dense)` * Algorithm should iterate on the values of matrix A and B for all dimensions and call `x(Aij..z,Bij..z)` * Output type is a `DenseMatrix` * `x()` operation invoked Z times, where Z is the number of elements in the matrix last dimension. For two dimensional matrix Z = MxN @@ -138,7 +143,7 @@ ###Algorithms for the implementation of element wise operations between a Dense Matrix and a Scalar Value: -- **Algorithm 14 `x(dense, scalar)`** +- **Algorithm 14 (Ds) `x(dense, scalar)`** * Algorithm should iterate on the values of matrix A for all dimensions and call `x(Aij..z, N)` * Output type is a `DenseMatrix` * `x()` operation invoked Z times, where Z is the number of elements in the matrix last dimension. diff --git a/src/type/matrix/utils/algorithm01.js b/src/type/matrix/utils/matAlgo01xDSid.js similarity index 96% rename from src/type/matrix/utils/algorithm01.js rename to src/type/matrix/utils/matAlgo01xDSid.js index 36498344b4..ba413a2b7e 100644 --- a/src/type/matrix/utils/algorithm01.js +++ b/src/type/matrix/utils/matAlgo01xDSid.js @@ -1,10 +1,10 @@ import { factory } from '../../../utils/factory.js' import { DimensionError } from '../../../error/DimensionError.js' -const name = 'algorithm01' +const name = 'matAlgo01xDSid' const dependencies = ['typed'] -export const createAlgorithm01 = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { +export const createMatAlgo01xDSid = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { /** * Iterates over SparseMatrix nonzero items and invokes the callback function f(Dij, Sij). * Callback function invoked NNZ times (number of nonzero items in SparseMatrix). diff --git a/src/type/matrix/utils/algorithm02.js b/src/type/matrix/utils/matAlgo02xDS0.js similarity index 93% rename from src/type/matrix/utils/algorithm02.js rename to src/type/matrix/utils/matAlgo02xDS0.js index 0563a0b588..f11dad48a7 100644 --- a/src/type/matrix/utils/algorithm02.js +++ b/src/type/matrix/utils/matAlgo02xDS0.js @@ -1,10 +1,10 @@ import { factory } from '../../../utils/factory.js' import { DimensionError } from '../../../error/DimensionError.js' -const name = 'algorithm02' +const name = 'matAlgo02xDS0' const dependencies = ['typed', 'equalScalar'] -export const createAlgorithm02 = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { +export const createMatAlgo02xDS0 = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { /** * Iterates over SparseMatrix nonzero items and invokes the callback function f(Dij, Sij). * Callback function invoked NNZ times (number of nonzero items in SparseMatrix). @@ -24,7 +24,7 @@ export const createAlgorithm02 = /* #__PURE__ */ factory(name, dependencies, ({ * * see https://github.com/josdejong/mathjs/pull/346#issuecomment-97477571 */ - return function algorithm02 (denseMatrix, sparseMatrix, callback, inverse) { + return function matAlgo02xDS0 (denseMatrix, sparseMatrix, callback, inverse) { // dense matrix arrays const adata = denseMatrix._data const asize = denseMatrix._size diff --git a/src/type/matrix/utils/algorithm03.js b/src/type/matrix/utils/matAlgo03xDSf.js similarity index 94% rename from src/type/matrix/utils/algorithm03.js rename to src/type/matrix/utils/matAlgo03xDSf.js index 9ff16f14c7..c3d4965270 100644 --- a/src/type/matrix/utils/algorithm03.js +++ b/src/type/matrix/utils/matAlgo03xDSf.js @@ -1,10 +1,10 @@ import { factory } from '../../../utils/factory.js' import { DimensionError } from '../../../error/DimensionError.js' -const name = 'algorithm03' +const name = 'matAlgo03xDSf' const dependencies = ['typed'] -export const createAlgorithm03 = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { +export const createMatAlgo03xDSf = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { /** * Iterates over SparseMatrix items and invokes the callback function f(Dij, Sij). * Callback function invoked M*N times. @@ -24,7 +24,7 @@ export const createAlgorithm03 = /* #__PURE__ */ factory(name, dependencies, ({ * * see https://github.com/josdejong/mathjs/pull/346#issuecomment-97477571 */ - return function algorithm03 (denseMatrix, sparseMatrix, callback, inverse) { + return function matAlgo03xDSf (denseMatrix, sparseMatrix, callback, inverse) { // dense matrix arrays const adata = denseMatrix._data const asize = denseMatrix._size diff --git a/src/type/matrix/utils/algorithm04.js b/src/type/matrix/utils/matAlgo04xSidSid.js similarity index 93% rename from src/type/matrix/utils/algorithm04.js rename to src/type/matrix/utils/matAlgo04xSidSid.js index 34315a391b..a98e4ba91a 100644 --- a/src/type/matrix/utils/algorithm04.js +++ b/src/type/matrix/utils/matAlgo04xSidSid.js @@ -1,18 +1,18 @@ import { factory } from '../../../utils/factory.js' import { DimensionError } from '../../../error/DimensionError.js' -const name = 'algorithm04' +const name = 'matAlgo04xSidSid' const dependencies = ['typed', 'equalScalar'] -export const createAlgorithm04 = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { +export const createMatAlgo04xSidSid = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { /** * Iterates over SparseMatrix A and SparseMatrix B nonzero items and invokes the callback function f(Aij, Bij). * Callback function invoked MAX(NNZA, NNZB) times * * * ┌ f(Aij, Bij) ; A(i,j) !== 0 && B(i,j) !== 0 - * C(i,j) = ┤ A(i,j) ; A(i,j) !== 0 - * └ B(i,j) ; B(i,j) !== 0 + * C(i,j) = ┤ A(i,j) ; A(i,j) !== 0 && B(i,j) === 0 + * └ B(i,j) ; A(i,j) === 0 * * * @param {Matrix} a The SparseMatrix instance (A) @@ -23,7 +23,7 @@ export const createAlgorithm04 = /* #__PURE__ */ factory(name, dependencies, ({ * * see https://github.com/josdejong/mathjs/pull/346#issuecomment-97620294 */ - return function algorithm04 (a, b, callback) { + return function matAlgo04xSidSid (a, b, callback) { // sparse matrix arrays const avalues = a._values const aindex = a._index diff --git a/src/type/matrix/utils/algorithm05.js b/src/type/matrix/utils/matAlgo05xSfSf.js similarity index 95% rename from src/type/matrix/utils/algorithm05.js rename to src/type/matrix/utils/matAlgo05xSfSf.js index 3eeb30e954..26af3300fc 100644 --- a/src/type/matrix/utils/algorithm05.js +++ b/src/type/matrix/utils/matAlgo05xSfSf.js @@ -1,10 +1,10 @@ import { factory } from '../../../utils/factory.js' import { DimensionError } from '../../../error/DimensionError.js' -const name = 'algorithm05' +const name = 'matAlgo05xSfSf' const dependencies = ['typed', 'equalScalar'] -export const createAlgorithm05 = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { +export const createMatAlgo05xSfSf = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { /** * Iterates over SparseMatrix A and SparseMatrix B nonzero items and invokes the callback function f(Aij, Bij). * Callback function invoked MAX(NNZA, NNZB) times @@ -23,7 +23,7 @@ export const createAlgorithm05 = /* #__PURE__ */ factory(name, dependencies, ({ * * see https://github.com/josdejong/mathjs/pull/346#issuecomment-97620294 */ - return function algorithm05 (a, b, callback) { + return function matAlgo05xSfSf (a, b, callback) { // sparse matrix arrays const avalues = a._values const aindex = a._index diff --git a/src/type/matrix/utils/algorithm06.js b/src/type/matrix/utils/matAlgo06xS0S0.js similarity index 95% rename from src/type/matrix/utils/algorithm06.js rename to src/type/matrix/utils/matAlgo06xS0S0.js index 8510c393ce..c07613e9fc 100644 --- a/src/type/matrix/utils/algorithm06.js +++ b/src/type/matrix/utils/matAlgo06xS0S0.js @@ -2,10 +2,10 @@ import { factory } from '../../../utils/factory.js' import { DimensionError } from '../../../error/DimensionError.js' import { scatter } from '../../../utils/collection.js' -const name = 'algorithm06' +const name = 'matAlgo06xS0S0' const dependencies = ['typed', 'equalScalar'] -export const createAlgorithm06 = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { +export const createMatAlgo06xS0S0 = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { /** * Iterates over SparseMatrix A and SparseMatrix B nonzero items and invokes the callback function f(Aij, Bij). * Callback function invoked (Anz U Bnz) times, where Anz and Bnz are the nonzero elements in both matrices. @@ -24,7 +24,7 @@ export const createAlgorithm06 = /* #__PURE__ */ factory(name, dependencies, ({ * * see https://github.com/josdejong/mathjs/pull/346#issuecomment-97620294 */ - return function algorithm06 (a, b, callback) { + return function matAlgo06xS0S0 (a, b, callback) { // sparse matrix arrays const avalues = a._values const asize = a._size diff --git a/src/type/matrix/utils/algorithm07.js b/src/type/matrix/utils/matAlgo07xSSf.js similarity index 94% rename from src/type/matrix/utils/algorithm07.js rename to src/type/matrix/utils/matAlgo07xSSf.js index a25530dd8d..a2e588f6eb 100644 --- a/src/type/matrix/utils/algorithm07.js +++ b/src/type/matrix/utils/matAlgo07xSSf.js @@ -1,10 +1,10 @@ import { factory } from '../../../utils/factory.js' import { DimensionError } from '../../../error/DimensionError.js' -const name = 'algorithm07' +const name = 'matAlgo07xSSf' const dependencies = ['typed', 'DenseMatrix'] -export const createAlgorithm07 = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix }) => { +export const createMatAlgo07xSSf = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix }) => { /** * Iterates over SparseMatrix A and SparseMatrix B items (zero and nonzero) and invokes the callback function f(Aij, Bij). * Callback function invoked MxN times. @@ -19,7 +19,7 @@ export const createAlgorithm07 = /* #__PURE__ */ factory(name, dependencies, ({ * * see https://github.com/josdejong/mathjs/pull/346#issuecomment-97620294 */ - return function algorithm07 (a, b, callback) { + return function matAlgo07xSSf (a, b, callback) { // sparse matrix arrays const asize = a._size const adt = a._datatype diff --git a/src/type/matrix/utils/algorithm08.js b/src/type/matrix/utils/matAlgo08xS0Sid.js similarity index 93% rename from src/type/matrix/utils/algorithm08.js rename to src/type/matrix/utils/matAlgo08xS0Sid.js index 7a8d8c2d02..b42f9bd185 100644 --- a/src/type/matrix/utils/algorithm08.js +++ b/src/type/matrix/utils/matAlgo08xS0Sid.js @@ -1,17 +1,17 @@ import { factory } from '../../../utils/factory.js' import { DimensionError } from '../../../error/DimensionError.js' -const name = 'algorithm08' +const name = 'matAlgo08xS0Sid' const dependencies = ['typed', 'equalScalar'] -export const createAlgorithm08 = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { +export const createMatAlgo08xS0Sid = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { /** * Iterates over SparseMatrix A and SparseMatrix B nonzero items and invokes the callback function f(Aij, Bij). * Callback function invoked MAX(NNZA, NNZB) times * * * ┌ f(Aij, Bij) ; A(i,j) !== 0 && B(i,j) !== 0 - * C(i,j) = ┤ A(i,j) ; A(i,j) !== 0 + * C(i,j) = ┤ A(i,j) ; A(i,j) !== 0 && B(i,j) === 0 * └ 0 ; otherwise * * @@ -23,7 +23,7 @@ export const createAlgorithm08 = /* #__PURE__ */ factory(name, dependencies, ({ * * see https://github.com/josdejong/mathjs/pull/346#issuecomment-97620294 */ - return function algorithm08 (a, b, callback) { + return function matAlgo08xS0Sid (a, b, callback) { // sparse matrix arrays const avalues = a._values const aindex = a._index diff --git a/src/type/matrix/utils/algorithm09.js b/src/type/matrix/utils/matAlgo09xS0Sf.js similarity index 94% rename from src/type/matrix/utils/algorithm09.js rename to src/type/matrix/utils/matAlgo09xS0Sf.js index 90cf4702ff..1cee1af7ed 100644 --- a/src/type/matrix/utils/algorithm09.js +++ b/src/type/matrix/utils/matAlgo09xS0Sf.js @@ -1,10 +1,10 @@ import { factory } from '../../../utils/factory.js' import { DimensionError } from '../../../error/DimensionError.js' -const name = 'algorithm09' +const name = 'matAlgo09xS0Sf' const dependencies = ['typed', 'equalScalar'] -export const createAlgorithm09 = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { +export const createMatAlgo09xS0Sf = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { /** * Iterates over SparseMatrix A and invokes the callback function f(Aij, Bij). * Callback function invoked NZA times, number of nonzero elements in A. @@ -23,7 +23,7 @@ export const createAlgorithm09 = /* #__PURE__ */ factory(name, dependencies, ({ * * see https://github.com/josdejong/mathjs/pull/346#issuecomment-97620294 */ - return function algorithm09 (a, b, callback) { + return function matAlgo09xS0Sf (a, b, callback) { // sparse matrix arrays const avalues = a._values const aindex = a._index diff --git a/src/type/matrix/utils/algorithm10.js b/src/type/matrix/utils/matAlgo10xSids.js similarity index 92% rename from src/type/matrix/utils/algorithm10.js rename to src/type/matrix/utils/matAlgo10xSids.js index 0c7eeaa220..a6fec597b9 100644 --- a/src/type/matrix/utils/algorithm10.js +++ b/src/type/matrix/utils/matAlgo10xSids.js @@ -1,9 +1,9 @@ import { factory } from '../../../utils/factory.js' -const name = 'algorithm10' +const name = 'matAlgo10xSids' const dependencies = ['typed', 'DenseMatrix'] -export const createAlgorithm10 = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix }) => { +export const createMatAlgo10xSids = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix }) => { /** * Iterates over SparseMatrix S nonzero items and invokes the callback function f(Sij, b). * Callback function invoked NZ times (number of nonzero items in S). @@ -23,7 +23,7 @@ export const createAlgorithm10 = /* #__PURE__ */ factory(name, dependencies, ({ * * https://github.com/josdejong/mathjs/pull/346#issuecomment-97626813 */ - return function algorithm10 (s, b, callback, inverse) { + return function matAlgo10xSids (s, b, callback, inverse) { // sparse matrix arrays const avalues = s._values const aindex = s._index diff --git a/src/type/matrix/utils/algorithm11.js b/src/type/matrix/utils/matAlgo11xS0s.js similarity index 93% rename from src/type/matrix/utils/algorithm11.js rename to src/type/matrix/utils/matAlgo11xS0s.js index a79cead99c..4b1a4f5a8e 100644 --- a/src/type/matrix/utils/algorithm11.js +++ b/src/type/matrix/utils/matAlgo11xS0s.js @@ -1,9 +1,9 @@ import { factory } from '../../../utils/factory.js' -const name = 'algorithm11' +const name = 'matAlgo11xS0s' const dependencies = ['typed', 'equalScalar'] -export const createAlgorithm11 = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { +export const createMatAlgo11xS0s = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar }) => { /** * Iterates over SparseMatrix S nonzero items and invokes the callback function f(Sij, b). * Callback function invoked NZ times (number of nonzero items in S). @@ -23,7 +23,7 @@ export const createAlgorithm11 = /* #__PURE__ */ factory(name, dependencies, ({ * * https://github.com/josdejong/mathjs/pull/346#issuecomment-97626813 */ - return function algorithm11 (s, b, callback, inverse) { + return function matAlgo11xS0s (s, b, callback, inverse) { // sparse matrix arrays const avalues = s._values const aindex = s._index diff --git a/src/type/matrix/utils/algorithm12.js b/src/type/matrix/utils/matAlgo12xSfs.js similarity index 93% rename from src/type/matrix/utils/algorithm12.js rename to src/type/matrix/utils/matAlgo12xSfs.js index dfa1812a85..edde61ceb3 100644 --- a/src/type/matrix/utils/algorithm12.js +++ b/src/type/matrix/utils/matAlgo12xSfs.js @@ -1,9 +1,9 @@ import { factory } from '../../../utils/factory.js' -const name = 'algorithm12' +const name = 'matAlgo12xSfs' const dependencies = ['typed', 'DenseMatrix'] -export const createAlgorithm12 = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix }) => { +export const createMatAlgo12xSfs = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix }) => { /** * Iterates over SparseMatrix S nonzero items and invokes the callback function f(Sij, b). * Callback function invoked MxN times. @@ -23,7 +23,7 @@ export const createAlgorithm12 = /* #__PURE__ */ factory(name, dependencies, ({ * * https://github.com/josdejong/mathjs/pull/346#issuecomment-97626813 */ - return function algorithm12 (s, b, callback, inverse) { + return function matAlgo12xSfs (s, b, callback, inverse) { // sparse matrix arrays const avalues = s._values const aindex = s._index diff --git a/src/type/matrix/utils/algorithm13.js b/src/type/matrix/utils/matAlgo13xDD.js similarity index 93% rename from src/type/matrix/utils/algorithm13.js rename to src/type/matrix/utils/matAlgo13xDD.js index ce1a064bd2..3ea14fd1c4 100644 --- a/src/type/matrix/utils/algorithm13.js +++ b/src/type/matrix/utils/matAlgo13xDD.js @@ -1,10 +1,10 @@ import { factory } from '../../../utils/factory.js' import { DimensionError } from '../../../error/DimensionError.js' -const name = 'algorithm13' +const name = 'matAlgo13xDD' const dependencies = ['typed'] -export const createAlgorithm13 = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { +export const createMatAlgo13xDD = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { /** * Iterates over DenseMatrix items and invokes the callback function f(Aij..z, Bij..z). * Callback function invoked MxN times. @@ -19,7 +19,7 @@ export const createAlgorithm13 = /* #__PURE__ */ factory(name, dependencies, ({ * * https://github.com/josdejong/mathjs/pull/346#issuecomment-97658658 */ - return function algorithm13 (a, b, callback) { + return function matAlgo13xDD (a, b, callback) { // a arrays const adata = a._data const asize = a._size diff --git a/src/type/matrix/utils/algorithm14.js b/src/type/matrix/utils/matAlgo14xDs.js similarity index 91% rename from src/type/matrix/utils/algorithm14.js rename to src/type/matrix/utils/matAlgo14xDs.js index d03382b883..342b2b19d9 100644 --- a/src/type/matrix/utils/algorithm14.js +++ b/src/type/matrix/utils/matAlgo14xDs.js @@ -1,10 +1,10 @@ import { factory } from '../../../utils/factory.js' import { clone } from '../../../utils/object.js' -const name = 'algorithm14' +const name = 'matAlgo14xDs' const dependencies = ['typed'] -export const createAlgorithm14 = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { +export const createMatAlgo14xDs = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { /** * Iterates over DenseMatrix items and invokes the callback function f(Aij..z, b). * Callback function invoked MxN times. @@ -20,7 +20,7 @@ export const createAlgorithm14 = /* #__PURE__ */ factory(name, dependencies, ({ * * https://github.com/josdejong/mathjs/pull/346#issuecomment-97659042 */ - return function algorithm14 (a, b, callback, inverse) { + return function matAlgo14xDs (a, b, callback, inverse) { // a arrays const adata = a._data const asize = a._size diff --git a/src/type/matrix/utils/matrixAlgorithmSuite.js b/src/type/matrix/utils/matrixAlgorithmSuite.js new file mode 100644 index 0000000000..549119e532 --- /dev/null +++ b/src/type/matrix/utils/matrixAlgorithmSuite.js @@ -0,0 +1,169 @@ +import { factory } from '../../../utils/factory.js' +import { extend } from '../../../utils/object.js' +import { createMatAlgo13xDD } from './matAlgo13xDD.js' +import { createMatAlgo14xDs } from './matAlgo14xDs.js' + +const name = 'matrixAlgorithmSuite' +const dependencies = ['typed', 'matrix'] + +export const createMatrixAlgorithmSuite = /* #__PURE__ */ factory( + name, dependencies, ({ typed, matrix }) => { + const matAlgo13xDD = createMatAlgo13xDD({ typed }) + const matAlgo14xDs = createMatAlgo14xDs({ typed }) + + /** + * Return a signatures object with the usual boilerplate of + * matrix algorithms, based on a plain options object with the + * following properties: + * elop: function -- the elementwise operation to use, defaults to self + * SS: function -- the algorithm to apply for two sparse matrices + * DS: function -- the algorithm to apply for a dense and a sparse matrix + * SD: function -- algo for a sparse and a dense; defaults to SD flipped + * Ss: function -- the algorithm to apply for a sparse matrix and scalar + * sS: function -- algo for scalar and sparse; defaults to Ss flipped + * scalar: string -- typed-function type for scalars, defaults to 'any' + * + * If Ss is not specified, no matrix-scalar signatures are generated. + * + * @param {object} options + * @return {Object} signatures + */ + return function matrixAlgorithmSuite (options) { + const elop = options.elop + const SD = options.SD || options.DS + let matrixSignatures + if (elop) { + // First the dense ones + matrixSignatures = { + 'DenseMatrix, DenseMatrix': (x, y) => matAlgo13xDD(x, y, elop), + 'Array, Array': (x, y) => + matAlgo13xDD(matrix(x), matrix(y), elop).valueOf(), + 'Array, DenseMatrix': (x, y) => matAlgo13xDD(matrix(x), y, elop), + 'DenseMatrix, Array': (x, y) => matAlgo13xDD(x, matrix(y), elop) + } + // Now incorporate sparse matrices + if (options.SS) { + matrixSignatures['SparseMatrix, SparseMatrix'] = + (x, y) => options.SS(x, y, elop, false) + } + if (options.DS) { + matrixSignatures['DenseMatrix, SparseMatrix'] = + (x, y) => options.DS(x, y, elop, false) + matrixSignatures['Array, SparseMatrix'] = + (x, y) => options.DS(matrix(x), y, elop, false) + } + if (SD) { + matrixSignatures['SparseMatrix, DenseMatrix'] = + (x, y) => SD(y, x, elop, true) + matrixSignatures['SparseMatrix, Array'] = + (x, y) => SD(matrix(y), x, elop, true) + } + } else { + // No elop, use this + // First the dense ones + matrixSignatures = { + 'DenseMatrix, DenseMatrix': typed.referToSelf(self => (x, y) => { + return matAlgo13xDD(x, y, self) + }), + 'Array, Array': typed.referToSelf(self => (x, y) => { + return matAlgo13xDD(matrix(x), matrix(y), self).valueOf() + }), + 'Array, DenseMatrix': typed.referToSelf(self => (x, y) => { + return matAlgo13xDD(matrix(x), y, self) + }), + 'DenseMatrix, Array': typed.referToSelf(self => (x, y) => { + return matAlgo13xDD(x, matrix(y), self) + }) + } + // Now incorporate sparse matrices + if (options.SS) { + matrixSignatures['SparseMatrix, SparseMatrix'] = + typed.referToSelf(self => (x, y) => { + return options.SS(x, y, self, false) + }) + } + if (options.DS) { + matrixSignatures['DenseMatrix, SparseMatrix'] = + typed.referToSelf(self => (x, y) => { + return options.DS(x, y, self, false) + }) + matrixSignatures['Array, SparseMatrix'] = + typed.referToSelf(self => (x, y) => { + return options.DS(matrix(x), y, self, false) + }) + } + if (SD) { + matrixSignatures['SparseMatrix, DenseMatrix'] = + typed.referToSelf(self => (x, y) => { + return SD(y, x, self, true) + }) + matrixSignatures['SparseMatrix, Array'] = + typed.referToSelf(self => (x, y) => { + return SD(matrix(y), x, self, true) + }) + } + } + + // Now add the scalars + const scalar = options.scalar || 'any' + const Ds = options.Ds || options.Ss + if (Ds) { + if (elop) { + matrixSignatures['DenseMatrix,' + scalar] = + (x, y) => matAlgo14xDs(x, y, elop, false) + matrixSignatures[scalar + ', DenseMatrix'] = + (x, y) => matAlgo14xDs(y, x, elop, true) + matrixSignatures['Array,' + scalar] = + (x, y) => matAlgo14xDs(matrix(x), y, elop, false).valueOf() + matrixSignatures[scalar + ', Array'] = + (x, y) => matAlgo14xDs(matrix(y), x, elop, true).valueOf() + } else { + matrixSignatures['DenseMatrix,' + scalar] = + typed.referToSelf(self => (x, y) => { + return matAlgo14xDs(x, y, self, false) + }) + matrixSignatures[scalar + ', DenseMatrix'] = + typed.referToSelf(self => (x, y) => { + return matAlgo14xDs(y, x, self, true) + }) + matrixSignatures['Array,' + scalar] = + typed.referToSelf(self => (x, y) => { + return matAlgo14xDs(matrix(x), y, self, false).valueOf() + }) + matrixSignatures[scalar + ', Array'] = + typed.referToSelf(self => (x, y) => { + return matAlgo14xDs(matrix(y), x, self, true).valueOf() + }) + } + } + const sS = (options.sS !== undefined) ? options.sS : options.Ss + if (elop) { + if (options.Ss) { + matrixSignatures['SparseMatrix,' + scalar] = + (x, y) => options.Ss(x, y, elop, false) + } + if (sS) { + matrixSignatures[scalar + ', SparseMatrix'] = + (x, y) => sS(y, x, elop, true) + } + } else { + if (options.Ss) { + matrixSignatures['SparseMatrix,' + scalar] = + typed.referToSelf(self => (x, y) => { + return options.Ss(x, y, self, false) + }) + } + if (sS) { + matrixSignatures[scalar + ', SparseMatrix'] = + typed.referToSelf(self => (x, y) => { + return sS(y, x, self, true) + }) + } + } + // Also pull in the scalar signatures if the operator is a typed function + if (elop && elop.signatures) { + extend(matrixSignatures, elop.signatures) + } + return matrixSignatures + } + }) diff --git a/src/type/number.js b/src/type/number.js index 6e142ec9fc..407b9593b6 100644 --- a/src/type/number.js +++ b/src/type/number.js @@ -128,9 +128,7 @@ export const createNumber = /* #__PURE__ */ factory(name, dependencies, ({ typed return unit.toNumber(valuelessUnit) }, - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) // reviver function to parse a JSON object like: diff --git a/src/type/string.js b/src/type/string.js index 4b35b5738c..85f57ff81c 100644 --- a/src/type/string.js +++ b/src/type/string.js @@ -50,9 +50,7 @@ export const createString = /* #__PURE__ */ factory(name, dependencies, ({ typed return x }, - 'Array | Matrix': function (x) { - return deepMap(x, this) - }, + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)), any: function (x) { return String(x) diff --git a/src/type/unit/Unit.js b/src/type/unit/Unit.js index 06ff2a02da..d00e5cd8dc 100644 --- a/src/type/unit/Unit.js +++ b/src/type/unit/Unit.js @@ -82,13 +82,7 @@ export const createUnitClass = /* #__PURE__ */ factory(name, dependencies, ({ this.units = u.units this.dimensions = u.dimensions } else { - this.units = [ - { - unit: UNIT_NONE, - prefix: PREFIXES.NONE, // link to a list with supported prefixes - power: 0 - } - ] + this.units = [] this.dimensions = [] for (let i = 0; i < BASE_DIMENSIONS.length; i++) { this.dimensions[i] = 0 @@ -109,6 +103,8 @@ export const createUnitClass = /* #__PURE__ */ factory(name, dependencies, ({ /** * Attach type information */ + Object.defineProperty(Unit, 'name', { value: 'Unit' }) + Unit.prototype.constructor = Unit Unit.prototype.type = 'Unit' Unit.prototype.isUnit = true @@ -464,6 +460,16 @@ export const createUnitClass = /* #__PURE__ */ factory(name, dependencies, ({ return unit } + /** + * Return the type of the value of this unit + * + * @memberof Unit + * @ return {string} type of the value of the unit + */ + Unit.prototype.valueType = function () { + return typeOf(this.value) + } + /** * Return whether the unit is derived (such as m/s, or cm^2, but not N) * @memberof Unit @@ -623,13 +629,14 @@ export const createUnitClass = /* #__PURE__ */ factory(name, dependencies, ({ } /** - * Multiply this unit with another one + * Multiply this unit with another one or with a scalar * @memberof Unit * @param {Unit} other * @return {Unit} product of this unit and the other unit */ - Unit.prototype.multiply = function (other) { + Unit.prototype.multiply = function (_other) { const res = this.clone() + const other = isUnit(_other) ? _other : new Unit(_other) for (let i = 0; i < BASE_DIMENSIONS.length; i++) { // Dimensions arrays may be of different lengths. Default to 0. @@ -654,19 +661,33 @@ export const createUnitClass = /* #__PURE__ */ factory(name, dependencies, ({ res.value = null } - res.skipAutomaticSimplification = false + if (isUnit(_other)) { + res.skipAutomaticSimplification = false + } return getNumericIfUnitless(res) } + /** + * Divide a number by this unit + * + * @memberof Unit + * @param {numeric} numerator + * @param {unit} result of dividing numerator by this unit + */ + Unit.prototype.divideInto = function (numerator) { + return new Unit(numerator).divide(this) + } + /** * Divide this unit by another one * @memberof Unit - * @param {Unit} other + * @param {Unit | numeric} other * @return {Unit} result of dividing this unit by the other unit */ - Unit.prototype.divide = function (other) { + Unit.prototype.divide = function (_other) { const res = this.clone() + const other = isUnit(_other) ? _other : new Unit(_other) for (let i = 0; i < BASE_DIMENSIONS.length; i++) { // Dimensions arrays may be of different lengths. Default to 0. @@ -692,7 +713,9 @@ export const createUnitClass = /* #__PURE__ */ factory(name, dependencies, ({ res.value = null } - res.skipAutomaticSimplification = false + if (isUnit(_other)) { + res.skipAutomaticSimplification = false + } return getNumericIfUnitless(res) } @@ -761,7 +784,7 @@ export const createUnitClass = /* #__PURE__ */ factory(name, dependencies, ({ // To give the correct, but unexpected, results for units with an offset. // For example, abs(-283.15 degC) = -263.15 degC !!! // We must take the offset into consideration here - const convert = Unit._getNumberConverter(typeOf(ret.value)) // convert to Fraction or BigNumber if needed + const convert = ret._numberConverter() // convert to Fraction or BigNumber if needed const unitValue = convert(ret.units[0].unit.value) const nominalOffset = convert(ret.units[0].unit.offset) const unitOffset = multiplyScalar(unitValue, nominalOffset) @@ -2972,6 +2995,21 @@ export const createUnitClass = /* #__PURE__ */ factory(name, dependencies, ({ } } + /** + * Retrieve the right converter function corresponding with this unit's + * value + * + * @memberof Unit + * @return {Function} + */ + Unit.prototype._numberConverter = function () { + const convert = Unit.typeConverters[this.valueType()] + if (convert) { + return convert + } + throw new TypeError('Unsupported Unit value type "' + this.valueType() + '"') + } + /** * Retrieve the right convertor function corresponding with the type * of provided exampleValue. diff --git a/src/type/unit/function/unit.js b/src/type/unit/function/unit.js index 108e643329..31203a0552 100644 --- a/src/type/unit/function/unit.js +++ b/src/type/unit/function/unit.js @@ -47,8 +47,11 @@ export const createUnitFunction = /* #__PURE__ */ factory(name, dependencies, ({ return new Unit(value, unit) }, - 'Array | Matrix': function (x) { - return deepMap(x, this) - } + 'number | BigNumber | Fraction': function (value) { + // dimensionless + return new Unit(value) + }, + + 'Array | Matrix': typed.referToSelf(self => x => deepMap(x, self)) }) }) diff --git a/src/utils/is.js b/src/utils/is.js index d4306b5837..1e9875baaa 100644 --- a/src/utils/is.js +++ b/src/utils/is.js @@ -153,6 +153,24 @@ export function isConstantNode (x) { return (x && x.isConstantNode === true && x.constructor.prototype.isNode === true) || false } +/* Very specialized: returns true for those nodes which in the numerator of + a fraction means that the division in that fraction has precedence over implicit + multiplication, e.g. -2/3 x parses as (-2/3) x and 3/4 x parses as (3/4) x but + 6!/8 x parses as 6! / (8x). It is located here because it is shared between + parse.js and OperatorNode.js (for parsing and printing, respectively). + + This should *not* be exported from mathjs, unlike most of the tests here. + Its name does not start with 'is' to prevent utils/snapshot.js from thinking + it should be exported. +*/ +export function rule2Node (node) { + return isConstantNode(node) || + (isOperatorNode(node) && + node.args.length === 1 && + isConstantNode(node.args[0]) && + '-+~'.includes(node.op)) +} + export function isFunctionAssignmentNode (x) { return (x && x.isFunctionAssignmentNode === true && x.constructor.prototype.isNode === true) || false } @@ -197,29 +215,12 @@ export function typeOf (x) { const t = typeof x if (t === 'object') { - // JavaScript types if (x === null) return 'null' - if (Array.isArray(x)) return 'Array' - if (x instanceof Date) return 'Date' - if (x instanceof RegExp) return 'RegExp' - - // math.js types - if (isBigNumber(x)) return 'BigNumber' - if (isComplex(x)) return 'Complex' - if (isFraction(x)) return 'Fraction' - if (isMatrix(x)) return 'Matrix' - if (isUnit(x)) return 'Unit' - if (isIndex(x)) return 'Index' - if (isRange(x)) return 'Range' - if (isResultSet(x)) return 'ResultSet' - if (isNode(x)) return x.type - if (isChain(x)) return 'Chain' - if (isHelp(x)) return 'Help' - - return 'Object' - } + if (isBigNumber(x)) return 'BigNumber' // Special: weird mashup with Decimal + if (x.constructor && x.constructor.name) return x.constructor.name - if (t === 'function') return 'Function' + return 'Object' // just in case + } - return t // can be 'string', 'number', 'boolean', ... + return t // can be 'string', 'number', 'boolean', 'function', 'bigint', ... } diff --git a/src/utils/snapshot.js b/src/utils/snapshot.js index f06444fc08..641efda755 100644 --- a/src/utils/snapshot.js +++ b/src/utils/snapshot.js @@ -10,6 +10,8 @@ import * as allIsFunctions from './is.js' import { create } from '../core/create.js' import { endsWith } from './string.js' +export const validateTypeOf = allIsFunctions.typeOf + export function validateBundle (expectedBundleStructure, bundle) { const originalWarn = console.warn @@ -107,24 +109,24 @@ export function createSnapshotFromFactories (factories) { const factory = factories[factoryName] const name = factory.fn const isTransformFunction = factory.meta && factory.meta.isTransformFunction - const isClass = !isLowerCase(name[0]) && (validateTypeOf(math[name]) === 'Function') + const isClass = !isLowerCase(name[0]) && (validateTypeOf(math[name]) === 'function') const dependenciesName = factory.fn + (isTransformFunction ? 'Transform' : '') + 'Dependencies' - allFactoryFunctions[factoryName] = 'Function' + allFactoryFunctions[factoryName] = 'function' allFunctionsConstantsClasses[name] = validateTypeOf(math[name]) allDependencyCollections[dependenciesName] = 'Object' if (isTransformFunction) { - allTransformFunctions[name] = 'Function' + allTransformFunctions[name] = 'function' } if (isClass) { if (endsWith(name, 'Node')) { - allNodeClasses[name] = 'Function' + allNodeClasses[name] = 'function' } else { - allClasses[name] = 'Function' + allClasses[name] = 'function' } } else { allFunctionsConstants[name] = validateTypeOf(math[name]) @@ -158,27 +160,27 @@ export function createSnapshotFromFactories (factories) { const allTypeChecks = {} Object.keys(allIsFunctions).forEach(name => { if (name.indexOf('is') === 0) { - allTypeChecks[name] = 'Function' + allTypeChecks[name] = 'function' } }) const allErrorClasses = { - ArgumentsError: 'Function', - DimensionError: 'Function', - IndexError: 'Function' + ArgumentsError: 'function', + DimensionError: 'function', + IndexError: 'function' } const expectedInstanceStructure = { ...allFunctionsConstantsClasses, - on: 'Function', - off: 'Function', - once: 'Function', - emit: 'Function', - import: 'Function', - config: 'Function', - create: 'Function', - factory: 'Function', + on: 'function', + off: 'function', + once: 'function', + emit: 'function', + import: 'function', + config: 'function', + create: 'function', + factory: 'function', ...allTypeChecks, ...allErrorClasses, @@ -193,7 +195,7 @@ export function createSnapshotFromFactories (factories) { ...exclude(allFunctionsConstants, [ 'chain' ]), - config: 'Function' + config: 'function' } } } @@ -209,9 +211,9 @@ export function createSnapshotFromFactories (factories) { 'PI', 'true' ]), - create: 'Function', - config: 'Function', - factory: 'Function', + create: 'function', + config: 'function', + factory: 'function', _true: 'boolean', _false: 'boolean', _null: 'null', @@ -232,34 +234,6 @@ export function createSnapshotFromFactories (factories) { } } -export function validateTypeOf (x) { - if (x && x.type === 'Unit') { - return 'Unit' - } - - if (x && x.type === 'Complex') { - return 'Complex' - } - - if (Array.isArray(x)) { - return 'Array' - } - - if (x === null) { - return 'null' - } - - if (typeof x === 'function') { - return 'Function' - } - - if (typeof x === 'object') { - return 'Object' - } - - return typeof x -} - function traverse (obj, callback = (value, path) => {}, path = []) { // FIXME: ugly to have these special cases if (path.length > 0 && path[0].indexOf('Dependencies') !== -1) { diff --git a/test/benchmark/load.js b/test/benchmark/load.js index 3d3235a487..97bddf4f67 100644 --- a/test/benchmark/load.js +++ b/test/benchmark/load.js @@ -14,6 +14,8 @@ function pad (text) { return padRight(text, 10, ' ') } +let calls + const suite = new Benchmark.Suite() suite .add(pad('load lazy'), function () { @@ -23,14 +25,15 @@ suite }) .add(pad('load all'), function () { const instance = math.create() - // force to load all lazy functions const everything = Object.values(instance) assert(everything.find(fn => fn.name === 'add')) + calls = instance.typed.createCount }) .on('cycle', function (event) { console.log(String(event.target)) }) .on('complete', function () { + console.log(`Load all created ${calls} typed functions.`) }) .run() diff --git a/test/browser-test-config/browserstack-karma.js b/test/browser-test-config/browserstack-karma.js index c68db931a7..ddd1c972e2 100644 --- a/test/browser-test-config/browserstack-karma.js +++ b/test/browser-test-config/browserstack-karma.js @@ -10,7 +10,6 @@ module.exports = function (config) { 'bs_firefox_windows', 'bs_chrome_mac', 'bs_safari_mac', - 'bs_ie_11', 'bs_edge' ], diff --git a/test/node-tests/browser.test.js b/test/node-tests/browser.test.js index f328b4013e..f1b341139a 100644 --- a/test/node-tests/browser.test.js +++ b/test/node-tests/browser.test.js @@ -32,6 +32,40 @@ describe('lib/browser', function () { console.warn = originalWarn }) + describe('typeOf should work on the minified bundle for all mathjs classes', function () { + const math = require('../../lib/browser/math.js') + + const typeOfTests = [ + { value: math.bignumber(2), expectedType: 'BigNumber' }, + { value: math.fraction(1, 3), expectedType: 'Fraction' }, + { value: math.fraction(1, 3), expectedType: 'Fraction' }, + { value: math.complex(2, 4), expectedType: 'Complex' }, + { value: math.unit('5 cm'), expectedType: 'Unit' }, + { value: math.matrix([], 'dense'), expectedType: 'DenseMatrix' }, + { value: math.matrix([], 'sparse'), expectedType: 'SparseMatrix' }, + { value: math.parse('A[2]'), expectedType: 'AccessorNode' }, + { value: math.parse('[1, 2, 3]'), expectedType: 'ArrayNode' }, + { value: math.parse('x = 2'), expectedType: 'AssignmentNode' }, + { value: math.parse('x = 2; y = 3'), expectedType: 'BlockNode' }, + { value: math.parse('x < 0 ? 0 : x'), expectedType: 'ConditionalNode' }, + { value: math.parse('3'), expectedType: 'ConstantNode' }, + { value: math.parse('f(x) = x^2'), expectedType: 'FunctionAssignmentNode' }, + { value: math.parse('sqrt(4)'), expectedType: 'FunctionNode' }, + { value: new math.IndexNode([]), expectedType: 'IndexNode' }, + { value: math.parse('{}'), expectedType: 'ObjectNode' }, + { value: math.parse('(2 + 3)'), expectedType: 'ParenthesisNode' }, + { value: math.parse('1:10'), expectedType: 'RangeNode' }, + { value: math.parse('2 > 3 > 4'), expectedType: 'RelationalNode' }, + { value: math.parse('2 + 3'), expectedType: 'OperatorNode' } + ] + + typeOfTests.forEach(({ value, expectedType }) => { + it(`typeOf should return ${expectedType}`, function () { + assert.strictEqual(math.typeOf(value), expectedType) + }) + }) + }) + it('should contain embedded docs for every function', function () { const math = require('../../lib/browser/math.js') diff --git a/test/node-tests/doc.test.js b/test/node-tests/doc.test.js index 5d7815bdfd..d8080ec28d 100644 --- a/test/node-tests/doc.test.js +++ b/test/node-tests/doc.test.js @@ -79,7 +79,7 @@ function extractValue (spec) { } const knownProblems = new Set([ - 'numeric', 'isZero', 'isPositive', 'isNumeric', 'isNegative', 'isNaN', + 'isZero', 'isPositive', 'isNumeric', 'isNegative', 'isNaN', 'isInteger', 'hasNumericValue', 'clone', 'print', 'hex', 'format', 'to', 'sin', 'cos', 'atan2', 'atan', 'asin', 'asec', 'acsc', 'acoth', 'acot', 'max', 'setUnion', 'unequal', 'equal', 'deepEqual', 'compareNatural', 'randomInt', @@ -92,7 +92,7 @@ const knownProblems = new Set([ 'mod', 'invmod', 'floor', 'fix', 'expm1', 'exp', 'dotPow', 'dotMultiply', 'dotDivide', 'divide', 'ceil', 'cbrt', 'add', 'usolveAll', 'usolve', 'slu', 'rationalize', 'qr', 'lusolve', 'lup', 'lsolveAll', 'lsolve', 'derivative', - 'simplifyCore', 'symbolicEqual', 'map', 'resolve' + 'symbolicEqual', 'map' ]) function maybeCheckExpectation (name, expected, expectedFrom, got, gotFrom) { diff --git a/test/node-tests/treeShaking/treeShaking.test.js b/test/node-tests/treeShaking/treeShaking.test.js index 874935cd0c..3caa884acd 100644 --- a/test/node-tests/treeShaking/treeShaking.test.js +++ b/test/node-tests/treeShaking/treeShaking.test.js @@ -65,7 +65,7 @@ describe('tree shaking', function () { // this may grow or shrink in the future assert.strictEqual(info.assets[0].name, bundleName) const size = info.assets[0].size - const maxSize = 120000 + const maxSize = 135000 assert(size < maxSize, 'bundled size must be small enough ' + '(actual size: ' + size + ' bytes, max size: ' + maxSize + ' bytes)') diff --git a/test/unit-tests/core/typed.test.js b/test/unit-tests/core/typed.test.js index a5ef5a0bd3..573c64241a 100644 --- a/test/unit-tests/core/typed.test.js +++ b/test/unit-tests/core/typed.test.js @@ -4,6 +4,16 @@ import Decimal from 'decimal.js' const math2 = math.create() describe('typed', function () { + it('should allow access to typed-function facilities', function () { + const fn = math.typed({ + identifier: () => 'variable', + string: () => 'expression' + }) + assert.strictEqual(fn('a96b2'), 'variable') + assert.strictEqual(fn('a96+b2'), 'expression') + assert.throws(() => fn(47), TypeError) + }) + // TODO: Move (most) of the type checks like isNumber, isComplex, to is.test.js it('should test whether a value is a number', function () { diff --git a/test/unit-tests/expression/node/AccessorNode.test.js b/test/unit-tests/expression/node/AccessorNode.test.js index d5872ec465..10b21cf9f8 100644 --- a/test/unit-tests/expression/node/AccessorNode.test.js +++ b/test/unit-tests/expression/node/AccessorNode.test.js @@ -32,7 +32,7 @@ describe('AccessorNode', function () { }) it('should throw an error when calling without new operator', function () { - assert.throws(function () { AccessorNode(new Node(), new IndexNode([])) }, SyntaxError) + assert.throws(() => AccessorNode(new Node(), new IndexNode([])), TypeError) }) it('should get the name of an AccessorNode', function () { diff --git a/test/unit-tests/expression/node/ArrayNode.test.js b/test/unit-tests/expression/node/ArrayNode.test.js index a41f0aba18..c27f55cb52 100644 --- a/test/unit-tests/expression/node/ArrayNode.test.js +++ b/test/unit-tests/expression/node/ArrayNode.test.js @@ -25,7 +25,7 @@ describe('ArrayNode', function () { }) it('should throw an error when calling without new operator', function () { - assert.throws(function () { ArrayNode() }, SyntaxError) + assert.throws(function () { ArrayNode() }, TypeError) }) it('should throw an error on wrong constructor arguments', function () { diff --git a/test/unit-tests/expression/node/AssignmentNode.test.js b/test/unit-tests/expression/node/AssignmentNode.test.js index 78debf9126..b48a26bd45 100644 --- a/test/unit-tests/expression/node/AssignmentNode.test.js +++ b/test/unit-tests/expression/node/AssignmentNode.test.js @@ -25,7 +25,8 @@ describe('AssignmentNode', function () { }) it('should throw an error when calling without new operator', function () { - assert.throws(function () { AssignmentNode(new SymbolNode('a'), new Node()) }, SyntaxError) + assert.throws( + () => AssignmentNode(new SymbolNode('a'), new Node()), TypeError) }) it('should throw an error when creating an AssignmentNode with a reserved keyword', function () { diff --git a/test/unit-tests/expression/node/BlockNode.test.js b/test/unit-tests/expression/node/BlockNode.test.js index c17bc333f7..f856f1332c 100644 --- a/test/unit-tests/expression/node/BlockNode.test.js +++ b/test/unit-tests/expression/node/BlockNode.test.js @@ -25,7 +25,7 @@ describe('BlockNode', function () { }) it('should throw an error when calling without new operator', function () { - assert.throws(function () { BlockNode() }, SyntaxError) + assert.throws(function () { BlockNode() }, TypeError) }) it('should throw an error when adding invalid blocks', function () { diff --git a/test/unit-tests/expression/node/ConditionalNode.test.js b/test/unit-tests/expression/node/ConditionalNode.test.js index b64a70f306..f8cd321989 100644 --- a/test/unit-tests/expression/node/ConditionalNode.test.js +++ b/test/unit-tests/expression/node/ConditionalNode.test.js @@ -30,7 +30,7 @@ describe('ConditionalNode', function () { }) it('should throw an error when calling without new operator', function () { - assert.throws(function () { ConditionalNode() }, SyntaxError) + assert.throws(function () { ConditionalNode() }, TypeError) }) it('should throw an error when creating without arguments', function () { diff --git a/test/unit-tests/expression/node/ConstantNode.test.js b/test/unit-tests/expression/node/ConstantNode.test.js index 5eaec3793c..4a6cef45fd 100644 --- a/test/unit-tests/expression/node/ConstantNode.test.js +++ b/test/unit-tests/expression/node/ConstantNode.test.js @@ -28,7 +28,7 @@ describe('ConstantNode', function () { }) it('should throw an error when calling without new operator', function () { - assert.throws(function () { ConstantNode(3) }, SyntaxError) + assert.throws(function () { ConstantNode(3) }, TypeError) }) it('should compile a ConstantNode', function () { diff --git a/test/unit-tests/expression/node/FunctionAssignmentNode.test.js b/test/unit-tests/expression/node/FunctionAssignmentNode.test.js index c08cf7ffd2..35f6f5ece2 100644 --- a/test/unit-tests/expression/node/FunctionAssignmentNode.test.js +++ b/test/unit-tests/expression/node/FunctionAssignmentNode.test.js @@ -26,7 +26,8 @@ describe('FunctionAssignmentNode', function () { }) it('should throw an error when calling without new operator', function () { - assert.throws(function () { FunctionAssignmentNode('f', ['x'], new ConstantNode(2)) }, SyntaxError) + assert.throws( + () => FunctionAssignmentNode('f', ['x'], new ConstantNode(2)), TypeError) }) it('should throw an error on wrong constructor arguments', function () { diff --git a/test/unit-tests/expression/node/FunctionNode.test.js b/test/unit-tests/expression/node/FunctionNode.test.js index e58a4d6f0e..b294e1c4fc 100644 --- a/test/unit-tests/expression/node/FunctionNode.test.js +++ b/test/unit-tests/expression/node/FunctionNode.test.js @@ -31,7 +31,7 @@ describe('FunctionNode', function () { it('should throw an error when calling without new operator', function () { const s = new SymbolNode('sqrt') const c = new ConstantNode(4) - assert.throws(function () { FunctionNode(s, [c]) }, SyntaxError) + assert.throws(function () { FunctionNode(s, [c]) }, TypeError) }) it('should throw an error when calling with wrong arguments', function () { diff --git a/test/unit-tests/expression/node/IndexNode.test.js b/test/unit-tests/expression/node/IndexNode.test.js index ba23855e9a..6ecb9b6916 100644 --- a/test/unit-tests/expression/node/IndexNode.test.js +++ b/test/unit-tests/expression/node/IndexNode.test.js @@ -29,7 +29,7 @@ describe('IndexNode', function () { }) it('should throw an error when calling without new operator', function () { - assert.throws(function () { IndexNode([]) }, SyntaxError) + assert.throws(function () { IndexNode([]) }, TypeError) }) it('should filter an IndexNode', function () { diff --git a/test/unit-tests/expression/node/Node.test.js b/test/unit-tests/expression/node/Node.test.js index e9d0f1a2be..2f6fe1d4c6 100644 --- a/test/unit-tests/expression/node/Node.test.js +++ b/test/unit-tests/expression/node/Node.test.js @@ -25,7 +25,7 @@ describe('Node', function () { }) it('should throw an error when calling without new operator', function () { - assert.throws(function () { Node() }, SyntaxError) + assert.throws(function () { Node() }, TypeError) }) it('should filter a Node', function () { @@ -212,7 +212,7 @@ describe('Node', function () { const node = new Node() assert.throws(function () { node.compile() - }, /Error: Method _compile should be implemented by type Node/) + }, /Error: Method _compile must be implemented by type Node/) }) it('should have an identifier', function () { diff --git a/test/unit-tests/expression/node/ObjectNode.test.js b/test/unit-tests/expression/node/ObjectNode.test.js index be801db082..f94f41e06d 100644 --- a/test/unit-tests/expression/node/ObjectNode.test.js +++ b/test/unit-tests/expression/node/ObjectNode.test.js @@ -27,7 +27,7 @@ describe('ObjectNode', function () { }) it('should throw an error when calling without new operator', function () { - assert.throws(function () { ObjectNode() }, SyntaxError) + assert.throws(function () { ObjectNode() }, TypeError) }) it('should throw an error on wrong constructor arguments', function () { @@ -280,7 +280,13 @@ describe('ObjectNode', function () { const n1 = new ObjectNode({ a, b }) const n2 = new ObjectNode({ c, n1 }) - assert.strictEqual(n2.toTex(), '\\left\\{\\begin{array}{ll}\\mathbf{c:} & 3\\\\\n\\mathbf{n1:} & \\left\\{\\begin{array}{ll}\\mathbf{a:} & 1\\\\\n\\mathbf{b:} & 2\\\\\\end{array}\\right\\}\\\\\\end{array}\\right\\}') + assert.strictEqual( + n2.toTex(), + '\\left\\{\\begin{array}{ll}' + + '\\mathbf{c:} & 3\\\\\n' + + '\\mathbf{n1:} & \\left\\{\\begin{array}{ll}' + + '\\mathbf{a:} & 1\\\\\n\\mathbf{b:} & 2\\\\\\end{array}\\right\\}\\\\' + + '\\end{array}\\right\\}') }) it('should LaTeX an ObjectNode with custom toTex', function () { @@ -294,6 +300,11 @@ describe('ObjectNode', function () { const b = new ConstantNode(2) const n = new ObjectNode({ a, b }) - assert.strictEqual(n.toTex({ handler: customFunction }), '\\left\\{\\begin{array}{ll}\\mathbf{a:} & const\\left(1, number\\right)\\\\\n\\mathbf{b:} & const\\left(2, number\\right)\\\\\\end{array}\\right\\}') + assert.strictEqual( + n.toTex({ handler: customFunction }), + '\\left\\{\\begin{array}{ll}' + + '\\mathbf{a:} & const\\left(1, number\\right)\\\\\n' + + '\\mathbf{b:} & const\\left(2, number\\right)\\\\' + + '\\end{array}\\right\\}') }) }) diff --git a/test/unit-tests/expression/node/OperatorNode.test.js b/test/unit-tests/expression/node/OperatorNode.test.js index f485d77485..11de90715f 100644 --- a/test/unit-tests/expression/node/OperatorNode.test.js +++ b/test/unit-tests/expression/node/OperatorNode.test.js @@ -7,6 +7,21 @@ const ConstantNode = math.ConstantNode const SymbolNode = math.SymbolNode const OperatorNode = math.OperatorNode const ConditionalNode = math.ConditionalNode +// Set up a bunch of expression pieces that are used over and over: +const one = new ConstantNode(1) +const two = new ConstantNode(2) +const three = new ConstantNode(3) +const four = new ConstantNode(4) +const five = new ConstantNode(5) +const add23 = new OperatorNode('+', 'add', [two, three]) +const sub23 = new OperatorNode('-', 'subtract', [two, three]) + +const asym = new SymbolNode('a') +const bsym = new SymbolNode('b') +const csym = new SymbolNode('c') +const dsym = new SymbolNode('d') +const xsym = new SymbolNode('x') +const ysym = new SymbolNode('y') describe('OperatorNode', function () { it('should create an OperatorNode', function () { @@ -22,36 +37,29 @@ describe('OperatorNode', function () { }) it('should throw an error when calling without new operator', function () { - const a = new ConstantNode(2) - const b = new ConstantNode(3) - assert.throws(function () { OperatorNode('+', 'add', [a, b]) }, SyntaxError) + assert.throws( + function () { OperatorNode('+', 'add', [two, three]) }, TypeError) }) it('should compile an OperatorNode', function () { - const a = new ConstantNode(2) - const b = new ConstantNode(3) - const n = new OperatorNode('+', 'add', [a, b]) - - const expr = n.compile() - - assert.strictEqual(expr.evaluate(), 5) + assert.strictEqual(add23.compile().evaluate(), 5) }) it('should test whether a unary or binary operator', function () { - const n1 = new OperatorNode('-', 'unaryMinus', [new ConstantNode(2)]) + const n1 = new OperatorNode('-', 'unaryMinus', [two]) assert.strictEqual(n1.isUnary(), true) assert.strictEqual(n1.isBinary(), false) // change the args of an operator node (bad practice, but should keep working correctly) - n1.args.push(new ConstantNode(3)) + n1.args.push(three) assert.strictEqual(n1.isUnary(), false) assert.strictEqual(n1.isBinary(), true) - const n2 = new OperatorNode('+', 'add', [new ConstantNode(2), new ConstantNode(3)]) + const n2 = new OperatorNode('+', 'add', [two, three]) assert.strictEqual(n2.isUnary(), false) assert.strictEqual(n2.isBinary(), true) - const n3 = new OperatorNode('+', 'add', [new ConstantNode(2), new ConstantNode(3), new ConstantNode(4)]) + const n3 = new OperatorNode('+', 'add', [two, three, four]) assert.strictEqual(n3.isUnary(), false) assert.strictEqual(n3.isBinary(), false) @@ -62,9 +70,7 @@ describe('OperatorNode', function () { }) it('should throw an error in case of unresolved operator function', function () { - const a = new ConstantNode(2) - const b = new ConstantNode(3) - const n = new OperatorNode('***', 'foo', [a, b]) + const n = new OperatorNode('***', 'foo', [two, three]) assert.throws(function () { n.compile() @@ -72,15 +78,11 @@ describe('OperatorNode', function () { }) it('should filter an OperatorNode', function () { - const a = new ConstantNode(2) - const b = new ConstantNode(3) - const n = new OperatorNode('+', 'add', [a, b]) - - assert.deepStrictEqual(n.filter(function (node) { return node instanceof OperatorNode }), [n]) - assert.deepStrictEqual(n.filter(function (node) { return node instanceof SymbolNode }), []) - assert.deepStrictEqual(n.filter(function (node) { return node instanceof ConstantNode }), [a, b]) - assert.deepStrictEqual(n.filter(function (node) { return node instanceof ConstantNode && node.value === 2 }), [a]) - assert.deepStrictEqual(n.filter(function (node) { return node instanceof ConstantNode && node.value === 4 }), []) + assert.deepStrictEqual(add23.filter(function (node) { return node instanceof OperatorNode }), [add23]) + assert.deepStrictEqual(add23.filter(function (node) { return node instanceof SymbolNode }), []) + assert.deepStrictEqual(add23.filter(function (node) { return node instanceof ConstantNode }), [two, three]) + assert.deepStrictEqual(add23.filter(function (node) { return node instanceof ConstantNode && node.value === 2 }), [two]) + assert.deepStrictEqual(add23.filter(function (node) { return node instanceof ConstantNode && node.value === 4 }), []) }) it('should filter an OperatorNode without contents', function () { @@ -92,10 +94,8 @@ describe('OperatorNode', function () { it('should run forEach on an OperatorNode', function () { // x^2-x - const a = new SymbolNode('x') - const b = new ConstantNode(2) - const c = new OperatorNode('^', 'pow', [a, b]) - const d = new SymbolNode('x') + const c = new OperatorNode('^', 'pow', [xsym, two]) + const d = new SymbolNode('x') // to make sure it's different from xsym const e = new OperatorNode('-', 'subtract', [c, d]) const nodes = [] @@ -114,21 +114,18 @@ describe('OperatorNode', function () { it('should map an OperatorNode', function () { // x^2-x - const a = new SymbolNode('x') - const b = new ConstantNode(2) - const c = new OperatorNode('^', 'pow', [a, b]) - const d = new SymbolNode('x') + const c = new OperatorNode('^', 'pow', [xsym, two]) + const d = new SymbolNode('x') // to make sure it's different from xsym const e = new OperatorNode('-', 'subtract', [c, d]) const nodes = [] const paths = [] - const f = new ConstantNode(3) const g = e.map(function (node, path, parent) { nodes.push(node) paths.push(path) assert.strictEqual(parent, e) - return node instanceof SymbolNode && node.name === 'x' ? f : node + return node instanceof SymbolNode && node.name === 'x' ? three : node }) assert.strictEqual(nodes.length, 2) @@ -138,23 +135,19 @@ describe('OperatorNode', function () { assert.notStrictEqual(g, e) assert.strictEqual(g.args[0], e.args[0]) - assert.strictEqual(g.args[0].args[0], a) // nested x is not replaced - assert.deepStrictEqual(g.args[0].args[1], b) - assert.deepStrictEqual(g.args[1], f) + assert.strictEqual(g.args[0].args[0], xsym) // nested x is not replaced + assert.deepStrictEqual(g.args[0].args[1], two) + assert.deepStrictEqual(g.args[1], three) }) it('should map an implicit OperatorNode', function () { - const x = new SymbolNode('x') - const y = new SymbolNode('y') - const product = new OperatorNode('*', 'multiply', [x, y], true /* implicit */) + const product = new OperatorNode('*', 'multiply', [xsym, ysym], true /* implicit */) assert.deepStrictEqual(product.map(function (x) { return x }), product) }) it('should throw an error when the map callback does not return a node', function () { - const a = new SymbolNode('x') - const b = new ConstantNode(2) - const c = new OperatorNode('^', 'pow', [a, b]) + const c = new OperatorNode('^', 'pow', [xsym, two]) assert.throws(function () { c.map(function () { return undefined }) @@ -163,40 +156,30 @@ describe('OperatorNode', function () { it('should transform an OperatorNodes parameters', function () { // x^2-x - const a = new SymbolNode('x') - const b = new ConstantNode(2) - const c = new OperatorNode('^', 'pow', [a, b]) - const d = new SymbolNode('x') + const c = new OperatorNode('^', 'pow', [xsym, two]) + const d = new SymbolNode('x') // to make sure it's different from xsym const e = new OperatorNode('-', 'subtract', [c, d]) - const f = new ConstantNode(3) const g = e.transform(function (node) { - return node instanceof SymbolNode && node.name === 'x' ? f : node + return node instanceof SymbolNode && node.name === 'x' ? three : node }) - assert.deepStrictEqual(g.args[1], f) + assert.deepStrictEqual(g.args[1], three) }) it('should transform an OperatorNode itself', function () { - // x^2-x - const a = new SymbolNode('x') - const b = new ConstantNode(2) - const c = new OperatorNode('+', 'add', [a, b]) + const c = new OperatorNode('+', 'add', [xsym, two]) - const f = new ConstantNode(3) const g = c.transform(function (node) { - return node instanceof OperatorNode ? f : node + return node instanceof OperatorNode ? three : node }) assert.notStrictEqual(g, c) - assert.deepStrictEqual(g, f) + assert.deepStrictEqual(g, three) }) it('should clone an OperatorNode', function () { - // x^2-x - const a = new SymbolNode('x') - const b = new ConstantNode(2) - const c = new OperatorNode('+', 'add', [a, b]) + const c = new OperatorNode('+', 'add', [xsym, two]) const d = c.clone() assert(d instanceof OperatorNode) @@ -208,9 +191,7 @@ describe('OperatorNode', function () { }) it('should clone implicit multiplications', function () { - const two = new ConstantNode(2) - const x = new SymbolNode('x') - const node = new OperatorNode('*', 'multiply', [two, x], true) + const node = new OperatorNode('*', 'multiply', [two, xsym], true) assert.strictEqual('2 x', node.toString()) assert.strictEqual(true, node.clone().implicit) @@ -218,6 +199,7 @@ describe('OperatorNode', function () { }) it('test equality another Node', function () { + // not using the standard instances to make sure everything is fresh const a = new OperatorNode('+', 'add', [new SymbolNode('x'), new ConstantNode(2)]) const b = new OperatorNode('+', 'add', [new SymbolNode('x'), new ConstantNode(2)]) const c = new OperatorNode('*', 'multiply', [new SymbolNode('x'), new ConstantNode(2)]) @@ -232,149 +214,157 @@ describe('OperatorNode', function () { assert.strictEqual(a.equals(e), false) }) - describe('toString', function () { - it('should stringify an OperatorNode', function () { - const a = new ConstantNode(2) - const b = new ConstantNode(3) + // Put a given expression through formatting paces: test its consistency, + // and results under toString and toTex, with various options: + // example is a object with either a key 'i' for input, or 'n' for Node + // (in the former case the value of 'i' is parsed to get the Node to test) + // and keys 's' and 'l' for the string and LaTex output, respectively. + // If the output is expected to differ for paren values of 'keep' and 'auto', + // then the keys 'skeep', 'sauto', 'lkeep', and 'lauto' can be used instead + // Takes optional 2nd argument that gives the list of paren values to try, + // defaults to ['keep', 'auto'] + function ex (example, parens = ['keep', 'auto']) { + const hasi = 'i' in example + const expr = hasi ? math.parse(example.i) : example.n + const orig = hasi + ? example.i + : `${expr.getIdentifier()}${expr.args.map(arg => arg.getIdentifier())}` + for (const paren of parens) { + const prefix = `${orig},${paren}: ` // eases reading of failure output + const skey = 's' in example ? 's' : 's' + paren + const lkey = 'l' in example ? 'l' : 'l' + paren + assert.strictEqual( + prefix + expr.toString({ parenthesis: paren }), prefix + example[skey]) + assert.strictEqual( + prefix + expr.toTex({ parenthesis: paren }), prefix + example[lkey]) + } + } - const n = new OperatorNode('+', 'add', [a, b]) - assert.strictEqual(n.toString(), '2 + 3') + describe('toString and toTex', function () { + it('on an OperatorNode', function () { + ex({ n: add23, s: '2 + 3', l: '2+3' }) }) - it('should stringify an OperatorNode with factorial', function () { - const a = new ConstantNode(2) - const n = new OperatorNode('!', 'factorial', [a]) - assert.strictEqual(n.toString(), '2!') + it('on an OperatorNode with factorial', function () { + ex({ n: new OperatorNode('!', 'factorial', [two]), s: '2!', l: '2!' }) }) - it('should stringify an OperatorNode with unary minus', function () { - const a = new ConstantNode(2) - const n = new OperatorNode('-', 'unaryMinus', [a]) - assert.strictEqual(n.toString(), '-2') + it('on an OperatorNode with unary minus', function () { + ex({ n: new OperatorNode('-', 'unaryMinus', [two]), s: '-2', l: '-2' }) }) - it('should stringify an OperatorNode with zero arguments', function () { - const n = new OperatorNode('foo', 'foo', []) - assert.strictEqual(n.toString(), 'foo()') + it('on an OperatorNode with zero arguments', function () { + ex({ n: new OperatorNode('foo', 'foo', []), s: 'foo()', l: '\\mathrm{foo}\\left(\\right)' }) }) - it('should stringify an OperatorNode with more than two operators', function () { - const a = new ConstantNode(2) - const b = new ConstantNode(3) - const c = new ConstantNode(4) - - const n = new OperatorNode('foo', 'foo', [a, b, c]) - assert.strictEqual(n.toString(), 'foo(2, 3, 4)') + it('on an OperatorNode with more than two operators', function () { + ex({ + n: new OperatorNode('foo', 'foo', [two, three, four]), + s: 'foo(2, 3, 4)', + l: '\\mathrm{foo}\\left(2,3,4\\right)' + }) }) - it('should stringify addition and multiplication with more than two operands', function () { - const a = new SymbolNode('a') - const b = new SymbolNode('b') - const c = new SymbolNode('c') - - const add = new OperatorNode('+', 'add', [a, b, c]) - const multiply = new OperatorNode('*', 'multiply', [a, b, c]) - const implicitMultiply = new OperatorNode('*', 'multiply', [a, b, c], true) - + it('on addition and multiplication with more than two operands', function () { + // This is slightly different than most of the tests, so not using `ex` + const add = new OperatorNode('+', 'add', [asym, bsym, csym]) + const multiply = new OperatorNode('*', 'multiply', [asym, bsym, csym]) + const implicitMultiply = new OperatorNode('*', 'multiply', [asym, bsym, csym], true) assert.strictEqual(add.toString(), 'a + b + c') assert.strictEqual(multiply.toString(), 'a * b * c') + // The first two verify that implicit: hide is indeed the default assert.strictEqual(implicitMultiply.toString(), 'a b c') + assert.strictEqual(implicitMultiply.toString({ implicit: 'hide' }), 'a b c') + assert.strictEqual(implicitMultiply.toString({ implicit: 'show' }), 'a * b * c') + + assert.strictEqual(add.toTex(), ' a+\\mathrm{b}+ c') + assert.strictEqual(multiply.toTex(), ' a\\cdot\\mathrm{b}\\cdot c') + // The first two verify that implicit: hide is indeed the default + assert.strictEqual(implicitMultiply.toTex(), ' a~\\mathrm{b}~ c') + assert.strictEqual(implicitMultiply.toTex({ implicit: 'hide' }), ' a~\\mathrm{b}~ c') + assert.strictEqual(implicitMultiply.toTex({ implicit: 'show' }), ' a\\cdot\\mathrm{b}\\cdot c') }) - it('should stringify addition and multiplication with more than two operands including OperatorNode', function () { - const a = new SymbolNode('a') - const b = new SymbolNode('b') - const c = new SymbolNode('c') - const d = new SymbolNode('d') - - const mult = new OperatorNode('*', 'multiply', [a, b]) - const add = new OperatorNode('+', 'add', [a, b]) + it('on addition and multiplication with more than two operands including OperatorNode', function () { + const mult = new OperatorNode('*', 'multiply', [asym, bsym]) + const add = new OperatorNode('+', 'add', [asym, bsym]) - const multipleMultWithMult = new OperatorNode('*', 'multiply', [c, mult, d]) - const multipleMultWithAdd = new OperatorNode('*', 'multiply', [c, add, d]) - const multipleAddWithMult = new OperatorNode('+', 'add', [c, mult, d]) - const multipleAddWithAdd = new OperatorNode('+', 'add', [c, add, d]) + const multipleMultWithMult = new OperatorNode('*', 'multiply', [csym, mult, dsym]) + const multipleMultWithAdd = new OperatorNode('*', 'multiply', [csym, add, dsym]) + const multipleAddWithMult = new OperatorNode('+', 'add', [csym, mult, dsym]) + const multipleAddWithAdd = new OperatorNode('+', 'add', [csym, add, dsym]) - assert.strictEqual(multipleMultWithMult.toString(), 'c * a * b * d') - assert.strictEqual(multipleMultWithAdd.toString(), 'c * (a + b) * d') - assert.strictEqual(multipleAddWithMult.toString(), 'c + a * b + d') - assert.strictEqual(multipleAddWithAdd.toString(), 'c + a + b + d') + ex({ n: multipleMultWithMult, s: 'c * a * b * d', l: ' c\\cdot a\\cdot\\mathrm{b}\\cdot d' }) + ex({ n: multipleMultWithAdd, s: 'c * (a + b) * d', l: ' c\\cdot\\left( a+\\mathrm{b}\\right)\\cdot d' }) + ex({ n: multipleAddWithMult, s: 'c + a * b + d', l: ' c+ a\\cdot\\mathrm{b}+ d' }) + ex({ n: multipleAddWithAdd, s: 'c + a + b + d', l: ' c+ a+\\mathrm{b}+ d' }) }) - it('should stringify an OperatorNode that contains an operatornode with more than two operands', function () { - const a = new SymbolNode('a') - const b = new SymbolNode('b') - const c = new SymbolNode('c') - const d = new SymbolNode('d') - - const mult = new OperatorNode('*', 'multiply', [a, b, c]) - const add = new OperatorNode('+', 'add', [a, b, c]) + it('on an OperatorNode that contains an operatornode with more than two operands', function () { + const mult = new OperatorNode('*', 'multiply', [asym, bsym, csym]) + const add = new OperatorNode('+', 'add', [asym, bsym, csym]) - const addWithMult = new OperatorNode('+', 'add', [mult, d]) - const addWithAdd = new OperatorNode('+', 'add', [add, d]) - const multWithMult = new OperatorNode('*', 'multiply', [mult, d]) - const multWithAdd = new OperatorNode('*', 'multiply', [add, d]) + const addWithMult = new OperatorNode('+', 'add', [mult, dsym]) + const addWithAdd = new OperatorNode('+', 'add', [add, dsym]) + const multWithMult = new OperatorNode('*', 'multiply', [mult, dsym]) + const multWithAdd = new OperatorNode('*', 'multiply', [add, dsym]) - assert.strictEqual(addWithMult.toString(), 'a * b * c + d') - assert.strictEqual(addWithAdd.toString(), 'a + b + c + d') - assert.strictEqual(multWithMult.toString(), 'a * b * c * d') - assert.strictEqual(multWithAdd.toString(), '(a + b + c) * d') + ex({ n: addWithMult, s: 'a * b * c + d', l: ' a\\cdot\\mathrm{b}\\cdot c+ d' }) + ex({ n: addWithAdd, s: 'a + b + c + d', l: ' a+\\mathrm{b}+ c+ d' }) + ex({ n: multWithMult, s: 'a * b * c * d', l: ' a\\cdot\\mathrm{b}\\cdot c\\cdot d' }) + ex({ n: multWithAdd, s: '(a + b + c) * d', l: '\\left( a+\\mathrm{b}+ c\\right)\\cdot d' }) }) - it('should stringify an OperatorNode with nested operator nodes', function () { - const a = new ConstantNode(2) - const b = new ConstantNode(3) - const c = new ConstantNode(4) - const d = new ConstantNode(5) + it('on an OperatorNode with nested operator nodes', function () { + const sub45 = new OperatorNode('-', 'subtract', [four, five]) + const prod1 = new OperatorNode('*', 'multiply', [add23, sub45]) + const prod2 = new OperatorNode('*', 'multiply', [add23, four]) + const diff1 = new OperatorNode('-', 'subtract', [prod2, five]) - const n1 = new OperatorNode('+', 'add', [a, b]) - const n2 = new OperatorNode('-', 'subtract', [c, d]) - const n3 = new OperatorNode('*', 'multiply', [n1, n2]) - - assert.strictEqual(n1.toString(), '2 + 3') - assert.strictEqual(n2.toString(), '4 - 5') - assert.strictEqual(n3.toString(), '(2 + 3) * (4 - 5)') + ex({ n: sub45, s: '4 - 5', l: '4-5' }) + ex({ n: prod1, s: '(2 + 3) * (4 - 5)', l: '\\left(2+3\\right)\\cdot\\left(4-5\\right)' }) + ex({ n: diff1, s: '(2 + 3) * 4 - 5', l: '\\left(2+3\\right)\\cdot4-5' }) }) - it('should stringify left associative OperatorNodes that are associative with another Node', function () { - assert.strictEqual(math.parse('(a+b)+c').toString({ parenthesis: 'auto' }), 'a + b + c') - assert.strictEqual(math.parse('a+(b+c)').toString({ parenthesis: 'auto' }), 'a + b + c') - assert.strictEqual(math.parse('(a+b)-c').toString({ parenthesis: 'auto' }), 'a + b - c') - assert.strictEqual(math.parse('a+(b-c)').toString({ parenthesis: 'auto' }), 'a + b - c') + it('on left associative OperatorNodes that are associative with another Node', function () { + ex({ i: '(a+b)+c', skeep: '(a + b) + c', sauto: 'a + b + c', lkeep: '\\left( a+\\mathrm{b}\\right)+ c', lauto: ' a+\\mathrm{b}+ c' }) + ex({ i: 'a+(b+c)', skeep: 'a + (b + c)', sauto: 'a + b + c', lkeep: ' a+\\left(\\mathrm{b}+ c\\right)', lauto: ' a+\\mathrm{b}+ c' }) + ex({ i: '(a+b)-c', skeep: '(a + b) - c', sauto: 'a + b - c', lkeep: '\\left( a+\\mathrm{b}\\right)- c', lauto: ' a+\\mathrm{b}- c' }) + ex({ i: 'a+(b-c)', skeep: 'a + (b - c)', sauto: 'a + b - c', lkeep: ' a+\\left(\\mathrm{b}- c\\right)', lauto: ' a+\\mathrm{b}- c' }) - assert.strictEqual(math.parse('(a*b)*c').toString({ parenthesis: 'auto' }), 'a * b * c') - assert.strictEqual(math.parse('a*(b*c)').toString({ parenthesis: 'auto' }), 'a * b * c') - assert.strictEqual(math.parse('(a*b)/c').toString({ parenthesis: 'auto' }), 'a * b / c') - assert.strictEqual(math.parse('a*(b/c)').toString({ parenthesis: 'auto' }), 'a * b / c') + ex({ i: '(a*b)*c', skeep: '(a * b) * c', sauto: 'a * b * c', lkeep: '\\left( a\\cdot\\mathrm{b}\\right)\\cdot c', lauto: ' a\\cdot\\mathrm{b}\\cdot c' }) + ex({ i: 'a*(b*c)', skeep: 'a * (b * c)', sauto: 'a * b * c', lkeep: ' a\\cdot\\left(\\mathrm{b}\\cdot c\\right)', lauto: ' a\\cdot\\mathrm{b}\\cdot c' }) + ex({ i: '(a*b)/c', skeep: '(a * b) / c', sauto: 'a * b / c', lkeep: '\\frac{\\left( a\\cdot\\mathrm{b}\\right)}{ c}', lauto: '\\frac{ a\\cdot\\mathrm{b}}{ c}' }) + ex({ i: 'a*(b/c)', skeep: 'a * (b / c)', sauto: 'a * b / c', lkeep: ' a\\cdot\\left(\\frac{\\mathrm{b}}{ c}\\right)', lauto: ' a\\cdot\\frac{\\mathrm{b}}{ c}' }) }) - it('should stringify left associative OperatorNodes that are not associative with another Node', function () { - assert.strictEqual(math.parse('(a-b)-c').toString({ parenthesis: 'auto' }), 'a - b - c') - assert.strictEqual(math.parse('a-(b-c)').toString({ parenthesis: 'auto' }), 'a - (b - c)') - assert.strictEqual(math.parse('(a-b)+c').toString({ parenthesis: 'auto' }), 'a - b + c') - assert.strictEqual(math.parse('a-(b+c)').toString({ parenthesis: 'auto' }), 'a - (b + c)') + it('on left associative OperatorNodes that are not associative with another Node', function () { + ex({ i: '(a-b)-c', skeep: '(a - b) - c', sauto: 'a - b - c', lkeep: '\\left( a-\\mathrm{b}\\right)- c', lauto: ' a-\\mathrm{b}- c' }) + ex({ i: 'a-(b-c)', s: 'a - (b - c)', l: ' a-\\left(\\mathrm{b}- c\\right)' }) + ex({ i: '(a-b)+c', skeep: '(a - b) + c', sauto: 'a - b + c', lkeep: '\\left( a-\\mathrm{b}\\right)+ c', lauto: ' a-\\mathrm{b}+ c' }) + ex({ i: 'a-(b+c)', s: 'a - (b + c)', l: ' a-\\left(\\mathrm{b}+ c\\right)' }) - assert.strictEqual(math.parse('(a/b)/c').toString({ parenthesis: 'auto' }), 'a / b / c') - assert.strictEqual(math.parse('a/(b/c)').toString({ parenthesis: 'auto' }), 'a / (b / c)') - assert.strictEqual(math.parse('(a/b)*c').toString({ parenthesis: 'auto' }), 'a / b * c') - assert.strictEqual(math.parse('a/(b*c)').toString({ parenthesis: 'auto' }), 'a / (b * c)') + ex({ i: '(a/b)/c', skeep: '(a / b) / c', sauto: 'a / b / c', lkeep: '\\frac{\\left(\\frac{ a}{\\mathrm{b}}\\right)}{ c}', lauto: '\\frac{\\frac{ a}{\\mathrm{b}}}{ c}' }) + ex({ i: 'a/(b/c)', s: 'a / (b / c)', lkeep: '\\frac{ a}{\\left(\\frac{\\mathrm{b}}{ c}\\right)}', lauto: '\\frac{ a}{\\frac{\\mathrm{b}}{ c}}' }) + ex({ i: '(a/b)*c', skeep: '(a / b) * c', sauto: 'a / b * c', lkeep: '\\left(\\frac{ a}{\\mathrm{b}}\\right)\\cdot c', lauto: '\\frac{ a}{\\mathrm{b}}\\cdot c' }) + ex({ i: 'a/(b*c)', s: 'a / (b * c)', lkeep: '\\frac{ a}{\\left(\\mathrm{b}\\cdot c\\right)}', lauto: '\\frac{ a}{\\mathrm{b}\\cdot c}' }) }) - it('should stringify right associative OperatorNodes that are not associative with another Node', function () { - assert.strictEqual(math.parse('(a^b)^c').toString({ parenthesis: 'auto' }), '(a ^ b) ^ c') - assert.strictEqual(math.parse('a^(b^c)').toString({ parenthesis: 'auto' }), 'a ^ b ^ c') + it('on right associative OperatorNodes that are not associative with another Node', function () { + ex({ i: '(a^b)^c', s: '(a ^ b) ^ c', l: '{\\left({ a}^{\\mathrm{b}}\\right)}^{ c}' }) + ex({ i: 'a^(b^c)', skeep: 'a ^ (b ^ c)', sauto: 'a ^ b ^ c', lkeep: '{ a}^{\\left({\\mathrm{b}}^{ c}\\right)}', lauto: '{ a}^{{\\mathrm{b}}^{ c}}' }) }) - it('should stringify unary OperatorNodes containing a binary OperatorNode', function () { - assert.strictEqual(math.parse('(a*b)!').toString(), '(a * b)!') - assert.strictEqual(math.parse('-(a*b)').toString(), '-(a * b)') - assert.strictEqual(math.parse('-(a+b)').toString(), '-(a + b)') + it('on unary OperatorNodes containing a binary OperatorNode', function () { + ex({ i: '(a*b)!', s: '(a * b)!', l: '\\left( a\\cdot\\mathrm{b}\\right)!' }) + ex({ i: '-(a*b)', s: '-(a * b)', l: '-\\left( a\\cdot\\mathrm{b}\\right)' }) + ex({ i: '-(a+b)', s: '-(a + b)', l: '-\\left( a+\\mathrm{b}\\right)' }) }) - it('should stringify unary OperatorNodes containing a unary OperatorNode', function () { - assert.strictEqual(math.parse('(-a)!').toString({ parenthesis: 'auto' }), '(-a)!') - assert.strictEqual(math.parse('-(a!)').toString({ parenthesis: 'auto' }), '-a!') - assert.strictEqual(math.parse('-(-a)').toString({ parenthesis: 'auto' }), '-(-a)') + it('on unary OperatorNodes containing a unary OperatorNode', function () { + ex({ i: '(-a)!', s: '(-a)!', l: '\\left(- a\\right)!' }) + ex({ i: '-(a!)', skeep: '-(a!)', sauto: '-a!', lkeep: '-\\left( a!\\right)', lauto: '- a!' }) + ex({ i: '-(-a)', s: '-(-a)', l: '-\\left(- a\\right)' }) }) }) @@ -390,13 +380,9 @@ describe('OperatorNode', function () { } } - const a = new ConstantNode(1) - const b = new ConstantNode(2) - - const n1 = new OperatorNode('+', 'add', [a, b]) - const n2 = new OperatorNode('-', 'subtract', [a, b]) + const n2 = new OperatorNode('-', 'subtract', [one, two]) - assert.strictEqual(n1.toString({ handler: customFunction }), '+add(const(1, number), const(2, number))') + assert.strictEqual(add23.toString({ handler: customFunction }), '+add(const(2, number), const(3, number))') assert.strictEqual(n2.toString({ handler: customFunction }), '-subtract(const(1, number), const(2, number))') }) @@ -412,190 +398,54 @@ describe('OperatorNode', function () { } } - const a = new ConstantNode(1) - const b = new ConstantNode(2) - - const n = new OperatorNode('+', 'add', [a, b]) - - assert.strictEqual(n.toString({ handler: customFunction }), 'const(1, number)+add+const(2, number)') + assert.strictEqual(add23.toString({ handler: customFunction }), 'const(2, number)+add+const(3, number)') }) it('should respect the \'all\' parenthesis option', function () { - assert.strictEqual(math.parse('1+1+1').toString({ parenthesis: 'all' }), '(1 + 1) + 1') - assert.strictEqual(math.parse('1+1+1').toTex({ parenthesis: 'all' }), '\\left(1+1\\right)+1') - }) - - it('should correctly LaTeX fractions in \'all\' parenthesis mode', function () { - assert.strictEqual(math.parse('1/2/3').toTex({ parenthesis: 'all' }), '\\frac{\\left(\\frac{1}{2}\\right)}{3}') + ex({ i: '1+1+1', s: '(1 + 1) + 1', l: '\\left(1+1\\right)+1' }, ['all']) }) - it('should LaTeX an OperatorNode', function () { - const a = new ConstantNode(2) - const b = new ConstantNode(3) - - const n = new OperatorNode('+', 'add', [a, b]) - assert.strictEqual(n.toTex(), '2+3') - }) - - it('should LaTeX an OperatorNode with factorial', function () { - const a = new ConstantNode(2) - const n = new OperatorNode('!', 'factorial', [a]) - assert.strictEqual(n.toTex(), '2!') + it('should correctly format fractions in \'all\' parenthesis mode', function () { + ex({ i: '1/2/3', s: '(1 / 2) / 3', l: '\\frac{\\left(\\frac{1}{2}\\right)}{3}' }, + ['all']) }) - it('should LaTeX an OperatorNode with factorial of an OperatorNode', function () { - const a = new ConstantNode(2) - const b = new ConstantNode(3) + it('should format an OperatorNode with factorial of an OperatorNode', function () { + const mult23 = new OperatorNode('*', 'multiply', [two, three]) + const div23 = new OperatorNode('/', 'divide', [two, three]) - const sub = new OperatorNode('-', 'subtract', [a, b]) - const add = new OperatorNode('+', 'add', [a, b]) - const mult = new OperatorNode('*', 'multiply', [a, b]) - const div = new OperatorNode('/', 'divide', [a, b]) - - const n1 = new OperatorNode('!', 'factorial', [sub]) - const n2 = new OperatorNode('!', 'factorial', [add]) - const n3 = new OperatorNode('!', 'factorial', [mult]) - const n4 = new OperatorNode('!', 'factorial', [div]) - assert.strictEqual(n1.toTex(), '\\left(2-3\\right)!') - assert.strictEqual(n2.toTex(), '\\left(2+3\\right)!') - assert.strictEqual(n3.toTex(), '\\left(2\\cdot3\\right)!') - assert.strictEqual(n4.toTex(), '\\frac{2}{3}!') + const n1 = new OperatorNode('!', 'factorial', [sub23]) + const n2 = new OperatorNode('!', 'factorial', [add23]) + const n3 = new OperatorNode('!', 'factorial', [mult23]) + const n4 = new OperatorNode('!', 'factorial', [div23]) + ex({ n: n1, s: '(2 - 3)!', l: '\\left(2-3\\right)!' }) + ex({ n: n2, s: '(2 + 3)!', l: '\\left(2+3\\right)!' }) + ex({ n: n3, s: '(2 * 3)!', l: '\\left(2\\cdot3\\right)!' }) + ex({ n: n4, s: '(2 / 3)!', l: '\\frac{2}{3}!' }) }) - it('should LaTeX an OperatorNode with unary minus', function () { - const a = new ConstantNode(2) - const b = new ConstantNode(3) - - const sub = new OperatorNode('-', 'subtract', [a, b]) - const add = new OperatorNode('+', 'add', [a, b]) - - const n1 = new OperatorNode('-', 'unaryMinus', [a]) - const n2 = new OperatorNode('-', 'unaryMinus', [sub]) - const n3 = new OperatorNode('-', 'unaryMinus', [add]) - - assert.strictEqual(n1.toTex(), '-2') - assert.strictEqual(n2.toTex(), '-\\left(2-3\\right)') - assert.strictEqual(n3.toTex(), '-\\left(2+3\\right)') - }) - - it('should LaTeX an OperatorNode that subtracts an OperatorNode', function () { - const a = new ConstantNode(1) - const b = new ConstantNode(2) - const c = new ConstantNode(3) - - const sub = new OperatorNode('-', 'subtract', [b, c]) - const add = new OperatorNode('+', 'add', [b, c]) - - const n1 = new OperatorNode('-', 'subtract', [a, sub]) - const n2 = new OperatorNode('-', 'subtract', [a, add]) - - assert.strictEqual(n1.toTex(), '1-\\left(2-3\\right)') - assert.strictEqual(n2.toTex(), '1-\\left(2+3\\right)') - }) - - it('should LaTeX an OperatorNode with zero arguments', function () { - const n = new OperatorNode('foo', 'foo', []) - assert.strictEqual(n.toTex(), '\\mathrm{foo}\\left(\\right)') - }) - - it('should LaTeX an OperatorNode with more than two operators', function () { - const a = new ConstantNode(2) - const b = new ConstantNode(3) - const c = new ConstantNode(4) - - const n = new OperatorNode('foo', 'foo', [a, b, c]) - assert.strictEqual(n.toTex(), '\\mathrm{foo}\\left(2,3,4\\right)') - }) - - it('should LaTeX an OperatorNode with nested operator nodes', function () { - const a = new ConstantNode(2) - const b = new ConstantNode(3) - const c = new ConstantNode(4) - const d = new ConstantNode(5) - - const n1 = new OperatorNode('+', 'add', [a, b]) - const n2 = new OperatorNode('-', 'subtract', [c, d]) - const n3 = new OperatorNode('*', 'multiply', [n1, n2]) - - const m2 = new OperatorNode('*', 'multiply', [n1, c]) - const m3 = new OperatorNode('-', 'subtract', [m2, d]) - - assert.strictEqual(n1.toTex(), '2+3') - assert.strictEqual(n2.toTex(), '4-5') - assert.strictEqual(n3.toTex(), '\\left(2+3\\right)\\cdot\\left(4-5\\right)') - assert.strictEqual(m3.toTex(), '\\left(2+3\\right)\\cdot4-5') - }) - - it('should LaTeX addition and multiplication with more than two operands', function () { - const a = new SymbolNode('a') - const b = new SymbolNode('b') - const c = new SymbolNode('c') - - const add = new OperatorNode('+', 'add', [a, b, c]) - const multiply = new OperatorNode('*', 'multiply', [a, b, c]) - const implicitMultiply = new OperatorNode('*', 'multiply', [a, b, c], true) - - assert.strictEqual(add.toTex(), ' a+\\mathrm{b}+ c') - assert.strictEqual(multiply.toTex(), ' a\\cdot\\mathrm{b}\\cdot c') - assert.strictEqual(implicitMultiply.toTex(), ' a~\\mathrm{b}~ c') - }) - - it('should LaTeX addition and multiplication with more than two operands including OperatorNode', function () { - const a = new SymbolNode('a') - const b = new SymbolNode('b') - const c = new SymbolNode('c') - const d = new SymbolNode('d') - - const mult = new OperatorNode('*', 'multiply', [a, b]) - const add = new OperatorNode('+', 'add', [a, b]) - - const multipleMultWithMult = new OperatorNode('*', 'multiply', [c, mult, d]) - const multipleMultWithAdd = new OperatorNode('*', 'multiply', [c, add, d]) - const multipleAddWithMult = new OperatorNode('+', 'add', [c, mult, d]) - const multipleAddWithAdd = new OperatorNode('+', 'add', [c, add, d]) + it('should format an OperatorNode with unary minus', function () { + const n2 = new OperatorNode('-', 'unaryMinus', [sub23]) + const n3 = new OperatorNode('-', 'unaryMinus', [add23]) - assert.strictEqual(multipleMultWithMult.toTex(), ' c\\cdot a\\cdot\\mathrm{b}\\cdot d') - assert.strictEqual(multipleMultWithAdd.toTex(), ' c\\cdot\\left( a+\\mathrm{b}\\right)\\cdot d') - assert.strictEqual(multipleAddWithMult.toTex(), ' c+ a\\cdot\\mathrm{b}+ d') - assert.strictEqual(multipleAddWithAdd.toTex(), ' c+ a+\\mathrm{b}+ d') + ex({ n: n2, s: '-(2 - 3)', l: '-\\left(2-3\\right)' }) + ex({ n: n3, s: '-(2 + 3)', l: '-\\left(2+3\\right)' }) }) - it('should LaTeX an OperatorNode that contains an operatornode with more than two operands', function () { - const a = new SymbolNode('a') - const b = new SymbolNode('b') - const c = new SymbolNode('c') - const d = new SymbolNode('d') + it('should format an OperatorNode that subtracts an OperatorNode', function () { + const n1 = new OperatorNode('-', 'subtract', [one, sub23]) + const n2 = new OperatorNode('-', 'subtract', [one, add23]) - const mult = new OperatorNode('*', 'multiply', [a, b, c]) - const add = new OperatorNode('+', 'add', [a, b, c]) - - const addWithMult = new OperatorNode('+', 'add', [mult, d]) - const addWithAdd = new OperatorNode('+', 'add', [add, d]) - const multWithMult = new OperatorNode('*', 'multiply', [mult, d]) - const multWithAdd = new OperatorNode('*', 'multiply', [add, d]) - - assert.strictEqual(addWithMult.toTex(), ' a\\cdot\\mathrm{b}\\cdot c+ d') - assert.strictEqual(addWithAdd.toTex(), ' a+\\mathrm{b}+ c+ d') - assert.strictEqual(multWithMult.toTex(), ' a\\cdot\\mathrm{b}\\cdot c\\cdot d') - assert.strictEqual(multWithAdd.toTex(), '\\left( a+\\mathrm{b}+ c\\right)\\cdot d') + ex({ n: n1, s: '1 - (2 - 3)', l: '1-\\left(2-3\\right)' }) + ex({ n: n2, s: '1 - (2 + 3)', l: '1-\\left(2+3\\right)' }) }) - it('should LaTeX fractions with operators that are enclosed in parenthesis', function () { - const a = new ConstantNode(1) - const b = new ConstantNode(2) - - const add = new OperatorNode('+', 'add', [a, a]) - const frac = new OperatorNode('/', 'divide', [add, b]) - assert.strictEqual(frac.toTex(), '\\frac{1+1}{2}') + it('should format fractions with operators that are enclosed in parenthesis', function () { + ex({ n: new OperatorNode('/', 'divide', [add23, four]), s: '(2 + 3) / 4', l: '\\frac{2+3}{4}' }) }) it('should have an identifier', function () { - const a = new ConstantNode(1) - const b = new ConstantNode(2) - - const n = new OperatorNode('+', 'add', [a, b]) - - assert.strictEqual(n.getIdentifier(), 'OperatorNode:add') + assert.strictEqual(add23.getIdentifier(), 'OperatorNode:add') }) it('should LaTeX an OperatorNode with custom toTex', function () { @@ -610,14 +460,8 @@ describe('OperatorNode', function () { } } - const a = new ConstantNode(1) - const b = new ConstantNode(2) - - const n1 = new OperatorNode('+', 'add', [a, b]) - const n2 = new OperatorNode('-', 'subtract', [a, b]) - - assert.strictEqual(n1.toTex({ handler: customFunction }), '+add(const\\left(1, number\\right), const\\left(2, number\\right))') - assert.strictEqual(n2.toTex({ handler: customFunction }), '-subtract(const\\left(1, number\\right), const\\left(2, number\\right))') + assert.strictEqual(add23.toTex({ handler: customFunction }), '+add(const\\left(2, number\\right), const\\left(3, number\\right))') + assert.strictEqual(sub23.toTex({ handler: customFunction }), '-subtract(const\\left(2, number\\right), const\\left(3, number\\right))') }) it('should LaTeX an OperatorNode with custom toTex for a single operator', function () { @@ -632,83 +476,112 @@ describe('OperatorNode', function () { } } - const a = new ConstantNode(1) - const b = new ConstantNode(2) - - const n = new OperatorNode('+', 'add', [a, b]) - - assert.strictEqual(n.toTex({ handler: customFunction }), 'const\\left(1, number\\right)+add+const\\left(2, number\\right)') + assert.strictEqual(add23.toTex({ handler: customFunction }), 'const\\left(2, number\\right)+add+const\\left(3, number\\right)') }) - it('should LaTeX powers of fractions with parentheses', function () { - const a = new ConstantNode(1) - const frac = new OperatorNode('/', 'divide', [a, a]) - const pow = new OperatorNode('^', 'pow', [frac, a]) + it('should format powers of fractions with parentheses', function () { + const frac = new OperatorNode('/', 'divide', [one, one]) + const pow = new OperatorNode('^', 'pow', [frac, one]) - assert.strictEqual(pow.toTex(), '\\left({\\frac{1}{1}}\\right)^{1}') + ex({ n: pow, s: '(1 / 1) ^ 1', l: '\\left({\\frac{1}{1}}\\right)^{1}' }) }) - it('should LaTeX powers of conditions with parentheses', function () { - const a = new ConstantNode(1) - const cond = new ConditionalNode(a, a, a) - const pow = new OperatorNode('^', 'pow', [cond, a]) + it('should format powers of conditions with parentheses', function () { + const cond = new ConditionalNode(one, one, one) + const pow = new OperatorNode('^', 'pow', [cond, one]) - assert.strictEqual(pow.toTex(), '\\left({\\begin{cases} {1}, &\\quad{\\text{if }\\;1}\\\\{1}, &\\quad{\\text{otherwise}}\\end{cases}}\\right)^{1}') + ex({ n: pow, s: '(1 ? 1 : 1) ^ 1', l: '\\left({\\begin{cases} {1}, &\\quad{\\text{if }\\;1}\\\\{1}, &\\quad{\\text{otherwise}}\\end{cases}}\\right)^{1}' }) }) - it('should LaTeX simple expressions in \'auto\' mode', function () { + it('should format simple expressions in \'auto\' mode', function () { // this covers a bug that was triggered previously - assert.strictEqual(math.parse('1+(1+1)').toTex({ parenthesis: 'auto' }), '1+1+1') - }) - - it('should stringify implicit multiplications', function () { - const a = math.parse('4a') - const b = math.parse('4 a') - const c = math.parse('a b') - const d = math.parse('2a b') - const e = math.parse('a b c') - const f = math.parse('(2+3)a') - const g = math.parse('(2+3)2') - const h = math.parse('2(3+4)') - - assert.strictEqual(a.toString(), a.toString({ implicit: 'hide' })) - assert.strictEqual(a.toString({ implicit: 'hide' }), '4 a') - assert.strictEqual(a.toString({ implicit: 'show' }), '4 * a') - - assert.strictEqual(b.toString(), b.toString({ implicit: 'hide' })) - assert.strictEqual(b.toString({ implicit: 'hide' }), '4 a') - assert.strictEqual(b.toString({ implicit: 'show' }), '4 * a') - - assert.strictEqual(c.toString(), c.toString({ implicit: 'hide' })) - assert.strictEqual(c.toString({ implicit: 'hide' }), 'a b') - assert.strictEqual(c.toString({ implicit: 'show' }), 'a * b') - - assert.strictEqual(d.toString(), d.toString({ implicit: 'hide' })) - assert.strictEqual(d.toString({ implicit: 'hide' }), '2 a b') - assert.strictEqual(d.toString({ implicit: 'show' }), '2 * a * b') - - assert.strictEqual(e.toString(), e.toString({ implicit: 'hide' })) - assert.strictEqual(e.toString({ implicit: 'hide' }), 'a b c') - assert.strictEqual(e.toString({ implicit: 'show' }), 'a * b * c') - - assert.strictEqual(f.toString(), f.toString({ implicit: 'hide' })) - assert.strictEqual(f.toString({ implicit: 'hide' }), '(2 + 3) a') - assert.strictEqual(f.toString({ implicit: 'show' }), '(2 + 3) * a') - - assert.strictEqual(g.toString(), g.toString({ implicit: 'hide' })) - assert.strictEqual(g.toString({ implicit: 'hide' }), '(2 + 3) 2') - assert.strictEqual(g.toString({ implicit: 'show' }), '(2 + 3) * 2') - - assert.strictEqual(h.toString(), h.toString({ implicit: 'hide' })) - assert.strictEqual(h.toString({ implicit: 'hide' }), '2 (3 + 4)') - assert.strictEqual(h.toString({ implicit: 'show' }), '2 * (3 + 4)') + ex({ i: '1+(1+1)', skeep: '1 + (1 + 1)', sauto: '1 + 1 + 1', lkeep: '1+\\left(1+1\\right)', lauto: '1+1+1' }) + }) + + // Variant of the `ex` tester that also tests implicit hide and show + function exhs (example, parens = ['keep', 'auto']) { + const imps = ['hide', 'show'] + const hasi = 'i' in example + const expr = hasi ? math.parse(example.i) : example.n + const orig = hasi + ? example.i + : `${expr.getIdentifier()}${expr.args.map(arg => arg.getIdentifier())}` + for (const paren of parens) { + const skey = 's' in example ? 's' : 's' + paren + const lkey = 'l' in example ? 'l' : 'l' + paren + for (const i of [0, 1]) { + const prefix = `${orig},${paren},${imps[i]}: ` // eases reading of failure output + assert.strictEqual( + prefix + expr.toString({ parenthesis: paren, implicit: imps[i] }), + prefix + example[skey][i]) + assert.strictEqual( + prefix + expr.toTex({ parenthesis: paren, implicit: imps[i] }), + prefix + example[lkey][i]) + } + } + } + + it('should format implicit multiplications', function () { + exhs({ i: '4a', s: ['4 a', '4 * a'], l: ['4~ a', '4\\cdot a'] }) + exhs({ i: '4 a', s: ['4 a', '4 * a'], l: ['4~ a', '4\\cdot a'] }) + exhs({ i: 'a b', s: ['a b', 'a * b'], l: [' a~\\mathrm{b}', ' a\\cdot\\mathrm{b}'] }) + exhs({ i: '2a b', s: ['2 a b', '2 * a * b'], l: ['2~ a~\\mathrm{b}', '2\\cdot a\\cdot\\mathrm{b}'] }) + exhs({ i: 'a b c', s: ['a b c', 'a * b * c'], l: [' a~\\mathrm{b}~ c', ' a\\cdot\\mathrm{b}\\cdot c'] }) + exhs({ i: '(2+3)a', s: ['(2 + 3) a', '(2 + 3) * a'], l: ['\\left(2+3\\right)~ a', '\\left(2+3\\right)\\cdot a'] }) + exhs({ i: '(2+3)2', s: ['(2 + 3) 2', '(2 + 3) * 2'], l: ['\\left(2+3\\right)~2', '\\left(2+3\\right)\\cdot2'] }) + exhs({ i: '2(3+4)', s: ['2 (3 + 4)', '2 * (3 + 4)'], l: ['2~\\left(3+4\\right)', '2\\cdot\\left(3+4\\right)'] }) + exhs({ i: 'a / b c', s: ['a / b c', 'a / (b * c)'], l: ['\\frac{ a}{\\mathrm{b}~ c}', '\\frac{ a}{\\mathrm{b}\\cdot c}'] }) + exhs({ i: 'a / b c d', s: ['a / b c d', 'a / (b * c * d)'], l: ['\\frac{ a}{\\mathrm{b}~ c~ d}', '\\frac{ a}{\\mathrm{b}\\cdot c\\cdot d}'] }) + exhs({ i: '1/2 a', s: ['1 / 2 a', '1 / 2 * a'], l: ['\\frac{1}{2}~ a', '\\frac{1}{2}\\cdot a'] }) + exhs({ i: '-2/3 a', s: ['-2 / 3 a', '-2 / 3 * a'], l: ['\\frac{-2}{3}~ a', '\\frac{-2}{3}\\cdot a'] }) + exhs({ i: '2!/3 a', s: ['2! / 3 a', '2! / (3 * a)'], l: ['\\frac{2!}{3~ a}', '\\frac{2!}{3\\cdot a}'] }) + exhs({ i: '+2!/3 a', s: ['+2! / 3 a', '+2! / (3 * a)'], l: ['\\frac{+2!}{3~ a}', '\\frac{+2!}{3\\cdot a}'] }) + exhs({ i: '2/3! a', s: ['2 / 3! a', '2 / (3! * a)'], l: ['\\frac{2}{3!~ a}', '\\frac{2}{3!\\cdot a}'] }) + exhs({ i: '-2!/+3! a', s: ['-2! / +3! a', '-2! / (+3! * a)'], l: ['\\frac{-2!}{+3!~ a}', '\\frac{-2!}{+3!\\cdot a}'] }) + exhs({ i: '2/-3 a', s: ['2 / -3 a', '2 / (-3 * a)'], l: ['\\frac{2}{-3~ a}', '\\frac{2}{-3\\cdot a}'] }) + exhs({ i: '-(2+3)/3x', s: ['-(2 + 3) / 3 x', '-(2 + 3) / (3 * x)'], l: ['\\frac{-\\left(2+3\\right)}{3~ x}', '\\frac{-\\left(2+3\\right)}{3\\cdot x}'] }) + exhs({ i: '-2/(3+4)x', s: ['-2 / (3 + 4) x', '-2 / ((3 + 4) * x)'], l: ['\\frac{-2}{\\left(3+4\\right)~ x}', '\\frac{-2}{\\left(3+4\\right)\\cdot x}'] }) + exhs({ + i: '(2)/3x', + skeep: ['(2) / 3 x', '(2) / (3 * x)'], + sauto: ['2 / (3 x)', '2 / (3 * x)'], + lkeep: ['\\frac{\\left(2\\right)}{3~ x}', '\\frac{\\left(2\\right)}{3\\cdot x}'], + lauto: ['\\frac{2}{3~ x}', '\\frac{2}{3\\cdot x}'] + }) + exhs({ + i: '2/(3)x', + skeep: ['2 / (3) x', '2 / ((3) * x)'], + sauto: ['2 / (3 x)', '2 / (3 * x)'], + lkeep: ['\\frac{2}{\\left(3\\right)~ x}', '\\frac{2}{\\left(3\\right)\\cdot x}'], + lauto: ['\\frac{2}{3~ x}', '\\frac{2}{3\\cdot x}'] + }) + exhs({ + i: '(2)/(3)x', + skeep: ['(2) / (3) x', '(2) / ((3) * x)'], + sauto: ['2 / (3 x)', '2 / (3 * x)'], + lkeep: ['\\frac{\\left(2\\right)}{\\left(3\\right)~ x}', '\\frac{\\left(2\\right)}{\\left(3\\right)\\cdot x}'], + lauto: ['\\frac{2}{3~ x}', '\\frac{2}{3\\cdot x}'] + }) + exhs({ + i: '(2!)/(3)x', + skeep: ['(2!) / (3) x', '(2!) / ((3) * x)'], + sauto: ['2! / 3 x', '2! / (3 * x)'], + lkeep: ['\\frac{\\left(2!\\right)}{\\left(3\\right)~ x}', '\\frac{\\left(2!\\right)}{\\left(3\\right)\\cdot x}'], + lauto: ['\\frac{2!}{3~ x}', '\\frac{2!}{3\\cdot x}'] + }) + exhs({ + i: '(2!)/3x', + skeep: ['(2!) / 3 x', '(2!) / (3 * x)'], + sauto: ['2! / 3 x', '2! / (3 * x)'], + lkeep: ['\\frac{\\left(2!\\right)}{3~ x}', '\\frac{\\left(2!\\right)}{3\\cdot x}'], + lauto: ['\\frac{2!}{3~ x}', '\\frac{2!}{3\\cdot x}'] + }) }) it('toJSON and fromJSON', function () { - const b = new ConstantNode(1) - const c = new ConstantNode(2) - - const node = new OperatorNode('+', 'add', [b, c], true) + // There is no such thing as an implicit add node, really, but + // put toJSON really through its paces + const node = new OperatorNode('+', 'add', [one, two], true) const json = node.toJSON() @@ -716,7 +589,7 @@ describe('OperatorNode', function () { mathjs: 'OperatorNode', op: '+', fn: 'add', - args: [b, c], + args: [one, two], implicit: true, isPercentage: false }) @@ -725,49 +598,6 @@ describe('OperatorNode', function () { assert.deepStrictEqual(parsed, node) }) - it('should LaTeX implicit multiplications', function () { - const a = math.parse('4a') - const b = math.parse('4 a') - const c = math.parse('a b') - const d = math.parse('2a b') - const e = math.parse('a b c') - const f = math.parse('(2+3)a') - const g = math.parse('(2+3)2') - const h = math.parse('2(3+4)') - - assert.strictEqual(a.toTex(), a.toTex({ implicit: 'hide' })) - assert.strictEqual(a.toTex({ implicit: 'hide' }), '4~ a') - assert.strictEqual(a.toTex({ implicit: 'show' }), '4\\cdot a') - - assert.strictEqual(b.toTex(), b.toTex({ implicit: 'hide' })) - assert.strictEqual(b.toTex({ implicit: 'hide' }), '4~ a') - assert.strictEqual(b.toTex({ implicit: 'show' }), '4\\cdot a') - - assert.strictEqual(c.toTex(), c.toTex({ implicit: 'hide' })) - assert.strictEqual(c.toTex({ implicit: 'hide' }), ' a~\\mathrm{b}') - assert.strictEqual(c.toTex({ implicit: 'show' }), ' a\\cdot\\mathrm{b}') - - assert.strictEqual(d.toTex(), d.toTex({ implicit: 'hide' })) - assert.strictEqual(d.toTex({ implicit: 'hide' }), '2~ a~\\mathrm{b}') - assert.strictEqual(d.toTex({ implicit: 'show' }), '2\\cdot a\\cdot\\mathrm{b}') - - assert.strictEqual(e.toTex(), e.toTex({ implicit: 'hide' })) - assert.strictEqual(e.toTex({ implicit: 'hide' }), ' a~\\mathrm{b}~ c') - assert.strictEqual(e.toTex({ implicit: 'show' }), ' a\\cdot\\mathrm{b}\\cdot c') - - assert.strictEqual(f.toTex(), f.toTex({ implicit: 'hide' })) - assert.strictEqual(f.toTex({ implicit: 'hide' }), '\\left(2+3\\right)~ a') - assert.strictEqual(f.toTex({ implicit: 'show' }), '\\left(2+3\\right)\\cdot a') - - assert.strictEqual(g.toTex(), g.toTex({ implicit: 'hide' })) - assert.strictEqual(g.toTex({ implicit: 'hide' }), '\\left(2+3\\right)~2') - assert.strictEqual(g.toTex({ implicit: 'show' }), '\\left(2+3\\right)\\cdot2') - - assert.strictEqual(h.toTex(), h.toTex({ implicit: 'hide' })) - assert.strictEqual(h.toTex({ implicit: 'hide' }), '2~\\left(3+4\\right)') - assert.strictEqual(h.toTex({ implicit: 'show' }), '2\\cdot\\left(3+4\\right)') - }) - it('should HTML operators', function () { assert.strictEqual(math.parse('2 + 3').toHTML(), '2' + @@ -791,32 +621,73 @@ describe('OperatorNode', function () { ) }) - it('should stringify implicit multiplications between ConstantNodes with parentheses', function () { - const a = math.parse('(4)(4)(4)(4)') - const b = math.parse('4b*4(4)') - const c = math.parse('(4(4(4)))') - - assert.strictEqual(a.toString({ implicit: 'hide', parenthesis: 'auto' }), '(4) (4) (4) (4)') - assert.strictEqual(b.toString({ implicit: 'hide', parenthesis: 'auto' }), '4 b * 4 (4)') - assert.strictEqual(c.toString({ implicit: 'hide', parenthesis: 'auto' }), '4 (4 (4))') + it('should format implicit multiplications between ConstantNodes with parentheses', function () { + ex({ i: '(3)x', skeep: '(3) x', sauto: '3 x', lkeep: '\\left(3\\right)~ x', lauto: '3~ x' }) + ex({ + i: '(4)(4)(4)(4)', + skeep: '(4) (4) (4) (4)', + sauto: '4 (4) (4) (4)', + lkeep: '\\left(4\\right)~\\left(4\\right)~\\left(4\\right)~\\left(4\\right)', + lauto: '4~\\left(4\\right)~\\left(4\\right)~\\left(4\\right)' + }) + ex({ i: '4b*4(4)', s: '4 b * 4 (4)', l: '4~\\mathrm{b}\\cdot4~\\left(4\\right)' }) + ex({ + i: '(4(4(4)))', + skeep: '(4 (4 (4)))', + sauto: '4 (4 (4))', + lkeep: '\\left(4~\\left(4~\\left(4\\right)\\right)\\right)', + lauto: '4~\\left(4~\\left(4\\right)\\right)' + }) }) - it('should LaTeX implicit multiplications between ConstantNodes with parentheses', function () { - const a = math.parse('(4)(4)(4)(4)') - const b = math.parse('4b*4(4)') - const c = math.parse('(4(4(4)))') - - assert.strictEqual(a.toTex({ implicit: 'hide', parenthesis: 'auto' }), '\\left(4\\right)~\\left(4\\right)~\\left(4\\right)~\\left(4\\right)') - assert.strictEqual(b.toTex({ implicit: 'hide', parenthesis: 'auto' }), '4~\\mathrm{b}\\cdot4~\\left(4\\right)') - assert.strictEqual(c.toTex({ implicit: 'hide', parenthesis: 'auto' }), '4~\\left(4~\\left(4\\right)\\right)') + it('should stringify implicit multiplications recoverably and to preserve their values', function () { + const m1 = new OperatorNode('-', 'unaryMinus', [one]) + const m2 = new OperatorNode('-', 'unaryMinus', [two]) + const p1 = new OperatorNode('+', 'unaryPlus', [one]) + const p2 = new OperatorNode('+', 'unaryPlus', [two]) + const onetwo = new OperatorNode('/', 'divide', [one, two]) + const m1two = new OperatorNode('/', 'divide', [m1, two]) + const p1two = new OperatorNode('/', 'divide', [p1, two]) + const onem2 = new OperatorNode('/', 'divide', [one, m2]) + const onep2 = new OperatorNode('/', 'divide', [one, p2]) + const onePlus2 = new OperatorNode('+', 'add', [one, two]) + const onePlusm2 = new OperatorNode('+', 'add', [one, m2]) + const onePlus2over2 = new OperatorNode('/', 'divide', [ + new OperatorNode('+', 'add', [one, two]), two]) + const twoOver1plus2 = new OperatorNode('/', 'divide', [ + two, new OperatorNode('+', 'add', [one, two])]) + const avar = new SymbolNode('a') + const ascope = { a: 2 } + const cs = [ + onetwo, m1two, p1two, onem2, onep2, + onePlus2, onePlusm2, onePlus2over2, twoOver1plus2] + for (const paren of ['auto', 'keep']) { + for (const coeff of cs) { + let expr = new math.OperatorNode('*', 'multiply', [coeff, avar], true) + let estring = expr.toString({ parenthesis: paren, implicit: 'hide' }) + const rexpr = math.parse(estring) + const rstring = rexpr.toString({ parenthesis: 'all' }) + // Make sure parsing the string version gives back the same grouping as the + // original: + assert.strictEqual(rstring, expr.toString({ parenthesis: 'all' })) + // And make sure that it produces the same value + assert.strictEqual(rexpr.evaluate(ascope), expr.evaluate(ascope)) + // And make sure that's the same value you get with a constant in the expression + expr = new math.OperatorNode('*', 'multiply', [coeff, two], true) + estring = expr.toString({ parenthesis: paren, implicit: 'hide' }) + assert.strictEqual(math.evaluate(estring, {}), expr.evaluate(ascope)) + } + } }) it('should HTML implicit multiplications between ConstantNodes with parentheses', function () { + const z = math.parse('(3)x') const a = math.parse('(4)(4)(4)(4)') const b = math.parse('4b*4(4)') const c = math.parse('(4(4(4)))') - assert.strictEqual(a.toHTML({ implicit: 'hide', parenthesis: 'auto' }), '(4)(4)(4)(4)') + assert.strictEqual(z.toHTML({ implicit: 'hide', parenthesis: 'auto' }), '3x') + assert.strictEqual(a.toHTML({ implicit: 'hide', parenthesis: 'auto' }), '4(4)(4)(4)') assert.strictEqual(b.toHTML({ implicit: 'hide', parenthesis: 'auto' }), '4b*4(4)') assert.strictEqual(c.toHTML({ implicit: 'hide', parenthesis: 'auto' }), '4(4(4))') }) diff --git a/test/unit-tests/expression/node/ParenthesisNode.test.js b/test/unit-tests/expression/node/ParenthesisNode.test.js index 2fe772a88f..b5ae673582 100644 --- a/test/unit-tests/expression/node/ParenthesisNode.test.js +++ b/test/unit-tests/expression/node/ParenthesisNode.test.js @@ -18,7 +18,7 @@ describe('ParenthesisNode', function () { it('should throw an error when calling without new operator', function () { const a = new ConstantNode(1) - assert.throws(function () { ParenthesisNode(a) }, SyntaxError) + assert.throws(function () { ParenthesisNode(a) }, TypeError) }) it('should throw an error when calling with wrong arguments', function () { diff --git a/test/unit-tests/expression/node/RangeNode.test.js b/test/unit-tests/expression/node/RangeNode.test.js index dfb32de546..00e07f20ec 100644 --- a/test/unit-tests/expression/node/RangeNode.test.js +++ b/test/unit-tests/expression/node/RangeNode.test.js @@ -29,7 +29,7 @@ describe('RangeNode', function () { it('should throw an error when calling without new operator', function () { const start = new ConstantNode(0) const end = new ConstantNode(10) - assert.throws(function () { RangeNode([start, end]) }, SyntaxError) + assert.throws(function () { RangeNode([start, end]) }, TypeError) }) it('should throw an error creating a RangeNode with wrong number or type of arguments', function () { diff --git a/test/unit-tests/expression/node/RelationalNode.test.js b/test/unit-tests/expression/node/RelationalNode.test.js index 645793b443..3f67550d60 100644 --- a/test/unit-tests/expression/node/RelationalNode.test.js +++ b/test/unit-tests/expression/node/RelationalNode.test.js @@ -26,7 +26,7 @@ describe('RelationalNode', function () { }) it('should throw an error when calling without new operator', function () { - assert.throws(function () { RelationalNode() }, SyntaxError) + assert.throws(function () { RelationalNode() }, TypeError) }) it('should throw an error when creating without arguments', function () { diff --git a/test/unit-tests/expression/node/SymbolNode.test.js b/test/unit-tests/expression/node/SymbolNode.test.js index b26856c9a7..cc4426d50e 100644 --- a/test/unit-tests/expression/node/SymbolNode.test.js +++ b/test/unit-tests/expression/node/SymbolNode.test.js @@ -21,7 +21,7 @@ describe('SymbolNode', function () { }) it('should throw an error when calling without new operator', function () { - assert.throws(function () { SymbolNode('sqrt') }, SyntaxError) + assert.throws(function () { SymbolNode('sqrt') }, TypeError) }) it('should throw an error when calling with wrong arguments', function () { diff --git a/test/unit-tests/expression/operators.test.js b/test/unit-tests/expression/operators.test.js index 2ef9640caa..48c51d42fb 100644 --- a/test/unit-tests/expression/operators.test.js +++ b/test/unit-tests/expression/operators.test.js @@ -1,6 +1,6 @@ import assert from 'assert' import math from '../../../src/defaultInstance.js' -import { getAssociativity, getPrecedence, isAssociativeWith } from '../../../src/expression/operators.js' +import { getAssociativity, getPrecedence, isAssociativeWith, getOperator } from '../../../src/expression/operators.js' const OperatorNode = math.OperatorNode const AssignmentNode = math.AssignmentNode const SymbolNode = math.SymbolNode @@ -15,9 +15,11 @@ describe('operators', function () { const n1 = new AssignmentNode(new SymbolNode('a'), a) const n2 = new OperatorNode('or', 'or', [a, b]) + const n3 = math.parse("M'") assert.strictEqual(getPrecedence(n1, 'keep'), 0) assert.strictEqual(getPrecedence(n2, 'keep'), 2) + assert.strictEqual(getPrecedence(n3, 'keep'), 18) }) it('should return null if precedence is not defined for a node', function () { @@ -45,11 +47,13 @@ describe('operators', function () { const n2 = new OperatorNode('^', 'pow', [a, a]) const n3 = new OperatorNode('-', 'unaryMinus', [a]) const n4 = new OperatorNode('!', 'factorial', [a]) + const n5 = math.parse("M'") assert.strictEqual(getAssociativity(n1, 'keep'), 'left') assert.strictEqual(getAssociativity(n2, 'keep'), 'right') assert.strictEqual(getAssociativity(n3, 'keep'), 'right') assert.strictEqual(getAssociativity(n4, 'keep'), 'left') + assert.strictEqual(getAssociativity(n5, 'keep'), 'left') }) it('should return the associativity of a ParenthesisNode', function () { @@ -110,4 +114,11 @@ describe('operators', function () { assert.strictEqual(isAssociativeWith(p, sub, 'auto'), true) assert.strictEqual(isAssociativeWith(p, sub, 'keep'), null) }) + + it('should get the operator of a function name', function () { + assert.strictEqual(getOperator('multiply'), '*') + assert.strictEqual(getOperator('ctranspose'), "'") + assert.strictEqual(getOperator('mod'), 'mod') + assert.strictEqual(getOperator('square'), null) + }) }) diff --git a/test/unit-tests/expression/parse.test.js b/test/unit-tests/expression/parse.test.js index 2d99efb027..c2bf5000c2 100644 --- a/test/unit-tests/expression/parse.test.js +++ b/test/unit-tests/expression/parse.test.js @@ -214,7 +214,7 @@ describe('parse', function () { }) it('should fill in the property comment of a Node', function () { - assert.strictEqual(parse('2 + 3').comment, '') + assert.strictEqual(parse('2 + 3').comment, undefined) assert.strictEqual(parse('2 + 3 # hello').comment, '# hello') assert.strictEqual(parse(' # hi').comment, '# hi') @@ -952,17 +952,17 @@ describe('parse', function () { it('should parse constants', function () { assert.strictEqual(parse('true').type, 'ConstantNode') - assert.deepStrictEqual(parse('true'), createConstantNode(true)) - assert.deepStrictEqual(parse('false'), createConstantNode(false)) - assert.deepStrictEqual(parse('null'), createConstantNode(null)) - assert.deepStrictEqual(parse('undefined'), createConstantNode(undefined)) + assert.deepStrictEqual(parse('true'), new ConstantNode(true)) + assert.deepStrictEqual(parse('false'), new ConstantNode(false)) + assert.deepStrictEqual(parse('null'), new ConstantNode(null)) + assert.deepStrictEqual(parse('undefined'), new ConstantNode(undefined)) }) it('should parse numeric constants', function () { const nanConstantNode = parse('NaN') assert.deepStrictEqual(nanConstantNode.type, 'ConstantNode') assert.ok(isNaN(nanConstantNode.value)) - assert.deepStrictEqual(parse('Infinity'), createConstantNode(Infinity)) + assert.deepStrictEqual(parse('Infinity'), new ConstantNode(Infinity)) }) it('should evaluate constants', function () { @@ -978,13 +978,6 @@ describe('parse', function () { assert.strictEqual(math.evaluate('true'), true) assert.strictEqual(math.evaluate('false'), false) }) - - // helper function to create a ConstantNode with empty comment - function createConstantNode (value) { - const c = new ConstantNode(value) - c.comment = '' - return c - } }) describe('variables', function () { @@ -1338,11 +1331,23 @@ describe('parse', function () { it('should follow precedence rules for implicit multiplication and division', function () { assert.strictEqual(parseAndStringifyWithParens('2 / 3 x'), '(2 / 3) x') + assert.strictEqual(parseAndStringifyWithParens('-2/3x'), '((-2) / 3) x') + assert.strictEqual(parseAndStringifyWithParens('+2/3x'), '((+2) / 3) x') + assert.strictEqual(parseAndStringifyWithParens('2!/3x'), '(2!) / (3 x)') + assert.strictEqual(parseAndStringifyWithParens('(2)/3x'), '2 / (3 x)') + assert.strictEqual(parseAndStringifyWithParens('2/3!x'), '2 / ((3!) x)') + assert.strictEqual(parseAndStringifyWithParens('2/(3)x'), '2 / (3 x)') + assert.strictEqual(parseAndStringifyWithParens('(2+4)/3x'), '(2 + 4) / (3 x)') + assert.strictEqual(parseAndStringifyWithParens('2/(3+4)x'), '2 / ((3 + 4) x)') assert.strictEqual(parseAndStringifyWithParens('2.5 / 5 kg'), '(2.5 / 5) kg') assert.strictEqual(parseAndStringifyWithParens('2.5 / 5 x y'), '((2.5 / 5) x) y') assert.strictEqual(parseAndStringifyWithParens('2 x / 5 y'), '(2 x) / (5 y)') assert.strictEqual(parseAndStringifyWithParens('17 h / 1 h'), '(17 h) / (1 h)') assert.strictEqual(parseAndStringifyWithParens('1 / 2 x'), '(1 / 2) x') + assert.strictEqual(parseAndStringifyWithParens('+1/2x'), '((+1) / 2) x') + assert.strictEqual(parseAndStringifyWithParens('~1/2x'), '((~1) / 2) x') + assert.strictEqual(parseAndStringifyWithParens('1 / -2 x'), '1 / ((-2) x)') + assert.strictEqual(parseAndStringifyWithParens('-1 / -2 x'), '(-1) / ((-2) x)') assert.strictEqual(parseAndStringifyWithParens('1 / 2 * x'), '(1 / 2) * x') assert.strictEqual(parseAndStringifyWithParens('1 / 2 x y'), '((1 / 2) x) y') assert.strictEqual(parseAndStringifyWithParens('1 / 2 (x y)'), '(1 / 2) (x y)') diff --git a/test/unit-tests/function/algebra/derivative.test.js b/test/unit-tests/function/algebra/derivative.test.js index eef686a2c7..4cfb775d4b 100644 --- a/test/unit-tests/function/algebra/derivative.test.js +++ b/test/unit-tests/function/algebra/derivative.test.js @@ -159,7 +159,7 @@ describe('derivative', function () { compareString(derivativeWithoutSimplify('asech((2x))', 'x'), '-(2 * 1) / ((2 x) * sqrt(1 - (2 x) ^ 2))') compareString(derivativeWithoutSimplify('acsch((2x))', 'x'), '-(2 * 1) / (abs((2 x)) * sqrt((2 x) ^ 2 + 1))') compareString(derivativeWithoutSimplify('acoth((2x))', 'x'), '-(2 * 1) / (1 - (2 x) ^ 2)') - compareString(derivativeWithoutSimplify('abs(2x)', 'x'), '2 * 1 * abs(2 x) / (2 x)') + compareString(derivativeWithoutSimplify('abs(2x)', 'x'), '2 * 1 * abs(2 x) / 2 x') // See power operator tests above compareString(derivativeWithoutSimplify('pow(0, 2^x + x^3 + 2)', 'x'), '0') @@ -245,7 +245,7 @@ describe('derivative', function () { it('should have controlled behavior on arguments errors', function () { assert.throws(function () { derivative('sqrt()', 'x') - }, /TypeError: Too few arguments in function sqrt \(expected: number or Complex or BigNumber or Unit or Array or Matrix or Fraction or string or boolean, index: 0\)/) + }, /TypeError: Too few arguments in function sqrt \(expected: number or Complex or BigNumber or Unit or Fraction or string or boolean, index: 0\)/) assert.throws(function () { derivative('sqrt(12, 2x)', 'x') }, /TypeError: Too many arguments in function sqrt \(expected: 1, actual: 2\)/) @@ -254,21 +254,21 @@ describe('derivative', function () { it('should throw error for incorrect argument types', function () { assert.throws(function () { derivative('42', '42') - }, /TypeError: Unexpected type of argument in function derivative \(expected: string or SymbolNode or number or boolean, actual: ConstantNode, index: 1\)/) + }, /TypeError: Unexpected type of argument in function derivative \(expected: SymbolNode or identifier, actual: string, index: 1\)/) assert.throws(function () { derivative('[1, 2; 3, 4]', 'x') - }, /TypeError: Unexpected type of argument in function constTag \(expected: ConstantNode or FunctionNode or FunctionAssignmentNode or OperatorNode or ParenthesisNode or SymbolNode, actual: ArrayNode, index: 1\)/) + }, /TypeError: Unexpected type of argument in function constTag \(expected: ConstantNode or FunctionNode or FunctionAssignmentNode or OperatorNode or ParenthesisNode or SymbolNode, actual:.*ArrayNode.*, index: 1\)/) assert.throws(function () { derivative('x + [1, 2; 3, 4]', 'x') - }, /TypeError: Unexpected type of argument in function constTag \(expected: ConstantNode or FunctionNode or FunctionAssignmentNode or OperatorNode or ParenthesisNode or SymbolNode, actual: ArrayNode, index: 1\)/) + }, /TypeError: Unexpected type of argument in function constTag \(expected: ConstantNode or FunctionNode or FunctionAssignmentNode or OperatorNode or ParenthesisNode or SymbolNode, actual:.*ArrayNode.*, index: 1\)/) }) it('should throw error if incorrect number of arguments', function () { assert.throws(function () { derivative('x + 2') - }, /TypeError: Too few arguments in function derivative \(expected: string or SymbolNode or number or boolean, index: 1\)/) + }, /TypeError: Too few arguments in function derivative \(expected: SymbolNode or identifier, index: 1\)/) assert.throws(function () { derivative('x + 2', 'x', {}, true, 42) diff --git a/test/unit-tests/function/algebra/resolve.test.js b/test/unit-tests/function/algebra/resolve.test.js index 53abb63b52..c0e73b7504 100644 --- a/test/unit-tests/function/algebra/resolve.test.js +++ b/test/unit-tests/function/algebra/resolve.test.js @@ -22,6 +22,10 @@ describe('resolve', function () { math.parse('[x,y,1,w]'), collapsingScope).toString(), '[z, z, 1, w]' ) + assert.strictEqual( + math.resolve('[x,y,1,w]', collapsingScope).toString(), + '[z, z, 1, w]' + ) simplifyAndCompare('x+y', 'x+y', {}) // operator simplifyAndCompare('x+y', 'y+1', { x: 1 }) simplifyAndCompare('x+y', 'y+1', { x: math.parse('1') }) @@ -39,27 +43,49 @@ describe('resolve', function () { simplifyAndCompare('size(text)[1]', '11', { text: 'hello world' }) }) + it('should operate directly on strings', function () { + const collapsingScope = { x: math.parse('y'), y: math.parse('z') } + assert.deepStrictEqual(math.resolve('x+y', { x: 1 }), math.parse('1 + y')) + assert.deepStrictEqual( + math.resolve('x + y', collapsingScope), + math.parse('z + z')) + assert.deepStrictEqual( + math.resolve('[x, y, 1, w]', collapsingScope), + math.parse('[z, z, 1, w]')) + }) + it('should substitute scoped constants from Map like scopes', function () { assert.strictEqual( math.resolve(math.parse('x+y'), new Map([['x', 1]])).toString(), '1 + y' ) // direct + assert.deepStrictEqual( + math.resolve('x+y', new Map([['x', 1]])), math.parse('1 + y')) simplifyAndCompare('x+y', 'x+y', new Map()) // operator simplifyAndCompare('x+y', 'y+1', new Map([['x', 1]])) simplifyAndCompare('x+y', 'y+1', new Map([['x', math.parse('1')]])) }) it('should resolve multiple nodes', function () { + const parse = math.parse const scope = { x: 1, y: 2 } - + const expressions = [parse('x+z'), 'y+z', 'y-x'] + let results = [parse('x+z'), parse('y+z'), parse('y-x')] + assert.deepStrictEqual(math.resolve(expressions), results) + results = [parse('1+z'), parse('2+z'), parse('2-1')] + assert.deepStrictEqual(math.resolve(expressions, scope), results) assert.deepStrictEqual( - math.resolve([ - math.parse('x+z'), - math.parse('y+z') - ], scope), - [ - math.resolve(math.parse('x+z'), scope), - math.resolve(math.parse('y+z'), scope) - ] + math.resolve(math.matrix(expressions), scope), + math.matrix(results) + ) + const nested = ['z/y', ['x+x', 'gcd(x,y)'], '3+x'] + results = [parse('z/2'), [parse('1+1'), parse('gcd(1,2)')], parse('3+1')] + assert.deepStrictEqual(math.resolve(nested, scope), results) + }) + + it('should throw a readable error if one item is wrong type', function () { + assert.throws( + () => math.resolve([math.parse('x'), 'y', 7]), + /TypeError: Unexpected.*actual: number, index: 0/ ) }) diff --git a/test/unit-tests/function/algebra/simplify.test.js b/test/unit-tests/function/algebra/simplify.test.js index 6c69d0dc6f..8c311c33dc 100644 --- a/test/unit-tests/function/algebra/simplify.test.js +++ b/test/unit-tests/function/algebra/simplify.test.js @@ -147,7 +147,7 @@ describe('simplify', function () { simplifyAndCompare('zeros(2,1)', '[0;0]') simplifyAndCompare('ones(3)', '[1,1,1]') simplifyAndCompare('identity(2)', '[1,0;0,1]') - simplifyAndCompare('sqrt([1,4,9])', '[1,2,3]') + simplifyAndCompare('floor([1.1,4.4,9.9])', '[1,4,9]') simplifyAndCompare('det([2,1;-1,3])', '7') simplifyAndCompare("[1,2;3,4]'", '[1,3;2,4]') }) @@ -182,7 +182,7 @@ describe('simplify', function () { simplifyAndCompare('2 - -3', '5') let e = math.parse('2 - -3') e = math.simplifyCore(e) - assert.strictEqual(e.toString(), '5') // simplifyCore + assert.strictEqual(e.toString(), '2 + 3') // simplifyCore simplifyAndCompare('x - -x', '2*x') e = math.parse('x - -x') e = math.simplifyCore(e) @@ -213,7 +213,7 @@ describe('simplify', function () { simplifyAndCompareEval('1 - 1e-10', '1 - 1e-10') simplifyAndCompareEval('1 + 1e-10', '1 + 1e-10') simplifyAndCompareEval('1e-10 / 2', '1e-10 / 2') - simplifyAndCompareEval('(1e-5)^2', '(1e-5)^2') + simplifyAndCompareEval('(1e-5)^2', '1e-10') simplifyAndCompareEval('min(1, -1e-10)', '-1e-10') simplifyAndCompareEval('max(1e-10, -1)', '1e-10') }) @@ -234,6 +234,19 @@ describe('simplify', function () { simplifyAndCompare('x^2*y^3*z - y*z*x^2*y', 'x^2*z*(y^3-y^2)') }) + it('can simplify with functions as well as operators', function () { + simplifyAndCompare('add(x,x)', '2*x') + simplifyAndCompare('multiply(x,2)+x', '3*x') + simplifyAndCompare('add(2*add(x,1), x+1)', '3*(x + 1)') + simplifyAndCompare('multiply(2, x+1) + add(x,1)', '3*(x + 1)') + simplifyAndCompare('add(y*pow(x,2), multiply(2,x^2))', 'x^2*(y+2)') + simplifyAndCompare('add(x*y, multiply(y,x))', '2*x*y') + simplifyAndCompare('subtract(multiply(x,y), multiply(y,x))', '0') + simplifyAndCompare('pow(x,2)*multiply(y^3, z) - multiply(y,z,y,x^2,y)', '0') + simplifyAndCompare('subtract(multiply(x^2, pow(y,3))*z, y*multiply(z,x^2)*y)', + 'x^2*z*(y^3-y^2)') + }) + it('should collect separated like terms', function () { simplifyAndCompare('x+1+x', '2*x+1') simplifyAndCompare('x^2+x+3+x^2', '2*x^2+x+3') @@ -274,7 +287,7 @@ describe('simplify', function () { it('should not run into an infinite recursive loop', function () { simplifyAndCompare('2n - 1', '2 n - 1') simplifyAndCompare('16n - 1', '16 n - 1') - simplifyAndCompare('16n / 1', '16 * n') + simplifyAndCompare('16n / 1', '16 n') simplifyAndCompare('8 / 5n', 'n * 8 / 5') simplifyAndCompare('8n - 4n', '4 * n') simplifyAndCompare('8 - 4n', '8 - 4 * n') @@ -497,16 +510,20 @@ describe('simplify', function () { } } + // Simplify actually increases accuracy when it uses fractions, so we + // disable that to get equality in these tests: + realContext.exactFractions = false + positiveContext.exactFractions = false for (const textExpr of expLibrary) { const expr = math.parse(textExpr) const realex = math.simplify(expr, {}, realContext) const posex = math.simplify(expr, {}, positiveContext) - assertAlike(expr.evaluate(zeroes), realex.evaluate(zeroes)) - assertAlike(expr.evaluate(negones), realex.evaluate(negones)) - assertAlike(expr.evaluate(ones), realex.evaluate(ones)) - assertAlike(expr.evaluate(twos), realex.evaluate(twos)) - assertAlike(expr.evaluate(ones), posex.evaluate(ones)) - assertAlike(expr.evaluate(twos), posex.evaluate(twos)) + assertAlike(realex.evaluate(zeroes), expr.evaluate(zeroes)) + assertAlike(realex.evaluate(negones), expr.evaluate(negones)) + assertAlike(realex.evaluate(ones), expr.evaluate(ones)) + assertAlike(realex.evaluate(twos), expr.evaluate(twos)) + assertAlike(posex.evaluate(ones), expr.evaluate(ones)) + assertAlike(posex.evaluate(twos), expr.evaluate(twos)) } // Make sure at least something is not equal const expr = math.parse('x/x') diff --git a/test/unit-tests/function/algebra/simplifyConstant.test.js b/test/unit-tests/function/algebra/simplifyConstant.test.js new file mode 100644 index 0000000000..04307beccb --- /dev/null +++ b/test/unit-tests/function/algebra/simplifyConstant.test.js @@ -0,0 +1,38 @@ +// test simplifyConstant +import assert from 'assert' + +import math from '../../../../src/defaultInstance.js' + +describe('simplifyConstant', function () { + const testSimplifyConstant = function (expr, expected, opts = {}, simpOpts = {}) { + let actual = math.simplifyConstant(math.parse(expr), simpOpts).toString(opts) + assert.strictEqual(actual, expected) + actual = math.simplifyConstant(expr, simpOpts).toString(opts) + assert.strictEqual(actual, expected) + } + + it('should evaluate constant subexpressions', function () { + testSimplifyConstant('2+2', '4') + testSimplifyConstant('x+3*5', 'x + 15') + testSimplifyConstant('f(sin(0))', 'f(0)') + testSimplifyConstant('[10/2, y, 8-4]', '[5, y, 4]') + }) + + it('should by default convert decimals into fractions', function () { + testSimplifyConstant('0.5 x', '1 / 2 x') + }) + + it('should coalesce constants in a multi-argument expression', function () { + testSimplifyConstant('3 + x + 7 + y', '10 + x + y') + testSimplifyConstant('3 * x * 7 * y', '21 * x * y') + }) + + it('should respect simplify options', function () { + testSimplifyConstant('0.5 x', '0.5 * x', { implicit: 'show' }, + { exactFractions: false }) + testSimplifyConstant('0.001 x', '0.001 * x', { implicit: 'show' }, + { fractionsLimit: 999 }) + testSimplifyConstant('3 * x * 7 * y', '3 * x * 7 * y', {}, + { context: { multiply: { commutative: false } } }) + }) +}) diff --git a/test/unit-tests/function/algebra/simplifyCore.test.js b/test/unit-tests/function/algebra/simplifyCore.test.js index a4b80efcf1..e4e5f4d371 100644 --- a/test/unit-tests/function/algebra/simplifyCore.test.js +++ b/test/unit-tests/function/algebra/simplifyCore.test.js @@ -4,14 +4,16 @@ import assert from 'assert' import math from '../../../../src/defaultInstance.js' describe('simplifyCore', function () { - const testSimplifyCore = function (expr, expected, opts = {}) { - const actual = math.simplifyCore(math.parse(expr)).toString(opts) + const testSimplifyCore = function (expr, expected, opts = {}, simpOpts = {}) { + let actual = math.simplifyCore(math.parse(expr), simpOpts).toString(opts) + assert.strictEqual(actual, expected) + actual = math.simplifyCore(expr, simpOpts).toString(opts) assert.strictEqual(actual, expected) } it('should handle different node types', function () { - testSimplifyCore('5*x*3', '15 * x') - testSimplifyCore('5*x*3*x', '15 * x * x') + testSimplifyCore('5*x*3', '3 * 5 * x') + testSimplifyCore('5*x*3*x', '3 * 5 * x * x') testSimplifyCore('x-0', 'x') testSimplifyCore('0-x', '-x') @@ -24,6 +26,16 @@ describe('simplifyCore', function () { testSimplifyCore('1*x', 'x') testSimplifyCore('-(x)', '-x') testSimplifyCore('0/x', '0') + testSimplifyCore('~~(a | b)', 'a | b') + testSimplifyCore('not (not (p and q))', 'p and q') + testSimplifyCore('1 and not done', 'not done') + testSimplifyCore('false and you(know, it)', 'false') + testSimplifyCore('(p or q) and "you"', 'p or q') + testSimplifyCore('something and ""', 'false') + testSimplifyCore('false or not(way)', 'not way') + testSimplifyCore('6 or dozen/2', 'true') + testSimplifyCore('(a and b) or 0', 'a and b') + testSimplifyCore('consequences or true', 'true') testSimplifyCore('(1*x + y*0)*1+0', 'x') testSimplifyCore('sin(x+0)*1', 'sin(x)') testSimplifyCore('((x+0)*1)', 'x') @@ -33,6 +45,14 @@ describe('simplifyCore', function () { testSimplifyCore('{a:x*1, b:y-0}', '{"a": x, "b": y}') }) + it('should not alter order of multiplication when noncommutative', function () { + testSimplifyCore('5*x*3', '5 * x * 3', {}, { context: { multiply: { commutative: false } } }) + }) + + it('should remove any trivial function', function () { + testSimplifyCore('foo(y)', 'y', {}, { context: { foo: { trivial: true } } }) + }) + it('strips ParenthesisNodes (implicit in tree)', function () { testSimplifyCore('((x)*(y))', 'x * y') testSimplifyCore('((x)*(y))^1', 'x * y') @@ -43,15 +63,8 @@ describe('simplifyCore', function () { testSimplifyCore('x+(y+z)+w', '(x + (y + z)) + w', { parenthesis: 'all' }) }) - it('folds constants', function () { - testSimplifyCore('1+2', '3') - testSimplifyCore('2*3', '6') - testSimplifyCore('2-3', '-1') - testSimplifyCore('3/2', '1.5') - testSimplifyCore('3^2', '9') - }) - it('should convert +unaryMinus to subtract', function () { + testSimplifyCore('x + -1', 'x - 1') const result = math.simplify( 'x + y + a', [math.simplifyCore], { a: -1 } ).toString() @@ -62,4 +75,24 @@ describe('simplifyCore', function () { testSimplifyCore('x+0==5', 'x == 5') testSimplifyCore('(x*1) % (y^1)', 'x % y') }) + + it('converts functions to their corresponding infix operators', function () { + testSimplifyCore('add(x, y)', 'x + y') + testSimplifyCore('mod(x, 5)', 'x mod 5') + testSimplifyCore('to(5 cm, in)', '5 cm to in') + testSimplifyCore('ctranspose(M)', "M'") + }) + + it('continues to simplify after function -> operator conversion', function () { + testSimplifyCore('add(multiply(x, 0), y)', 'y') + testSimplifyCore('and(multiply(1, x), true)', 'x and true') + testSimplifyCore('add(x, 0 ,y)', 'x + y') + }) + + it('can perform sequential distinct core simplifications', function () { + testSimplifyCore('0 - -x', 'x') + testSimplifyCore('0 - (x - y)', 'y - x') + testSimplifyCore('a + -0', 'a') + testSimplifyCore('-(-x - y)', 'y + x') + }) }) diff --git a/test/unit-tests/function/arithmetic/cbrt.test.js b/test/unit-tests/function/arithmetic/cbrt.test.js index 87e9668df6..06c0e0af89 100644 --- a/test/unit-tests/function/arithmetic/cbrt.test.js +++ b/test/unit-tests/function/arithmetic/cbrt.test.js @@ -111,10 +111,12 @@ describe('cbrt', function () { }) }) - it('should return the cubic root of each element of a matrix', function () { - assert.deepStrictEqual(cbrt([8, 27, 64, 125]), [2, 3, 4, 5]) - assert.deepStrictEqual(cbrt([[8, 27], [64, 125]]), [[2, 3], [4, 5]]) - assert.deepStrictEqual(cbrt(math.matrix([[8, 27], [64, 125]])), math.matrix([[2, 3], [4, 5]])) + it('should not operate on a matrix', function () { + assert.throws(() => cbrt([8, 27, 64, 125]), TypeError) + assert.throws(() => cbrt(math.matrix([8, 27])), TypeError) + assert.deepStrictEqual(math.map([8, 27, 64, 125], x => cbrt(x)), [2, 3, 4, 5]) + assert.deepStrictEqual(math.map([[8, 27], [64, 125]], x => cbrt(x)), [[2, 3], [4, 5]]) + assert.deepStrictEqual(math.map(math.matrix([[8, 27], [64, 125]]), x => cbrt(x)), math.matrix([[2, 3], [4, 5]])) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/arithmetic/cube.test.js b/test/unit-tests/function/arithmetic/cube.test.js index 10f2fd695a..e93ba1a30e 100644 --- a/test/unit-tests/function/arithmetic/cube.test.js +++ b/test/unit-tests/function/arithmetic/cube.test.js @@ -57,12 +57,12 @@ describe('cube', function () { assert.throws(function () { cube(null) }, /TypeError: Unexpected type of argument/) }) - it('should cube each element in a matrix, array or range', function () { - // array, matrix, range - // arrays are evaluated element wise - assert.deepStrictEqual(cube([2, 3, 4, 5]), [8, 27, 64, 125]) - assert.deepStrictEqual(cube(matrix([2, 3, 4, 5])), matrix([8, 27, 64, 125])) - assert.deepStrictEqual(cube(matrix([[1, 2], [3, 4]])), matrix([[1, 8], [27, 64]])) + it('should not operate on a matrix, array or range', function () { + assert.throws(() => cube([2, 3, 4, 5]), TypeError) + assert.throws(() => cube(matrix([2, 3, 4, 5])), TypeError) + assert.deepStrictEqual(math.map([2, 3, 4, 5], cube), [8, 27, 64, 125]) + assert.deepStrictEqual(math.map(matrix([2, 3, 4, 5]), cube), matrix([8, 27, 64, 125])) + assert.deepStrictEqual(math.map(matrix([[1, 2], [3, 4]]), cube), matrix([[1, 8], [27, 64]])) }) it('should LaTeX cube', function () { diff --git a/test/unit-tests/function/arithmetic/exp.test.js b/test/unit-tests/function/arithmetic/exp.test.js index 1e4faccb4e..b48de4b0a5 100644 --- a/test/unit-tests/function/arithmetic/exp.test.js +++ b/test/unit-tests/function/arithmetic/exp.test.js @@ -71,15 +71,17 @@ describe('exp', function () { assert.throws(function () { exp('text') }) }) - it('should exponentiate matrices, arrays and ranges correctly', function () { + it('should not operate on matrices, arrays and ranges', function () { // array - approx.deepEqual(exp([0, 1, 2, 3]), [1, 2.71828182845905, 7.38905609893065, 20.0855369231877]) - approx.deepEqual(exp([[0, 1], [2, 3]]), [[1, 2.71828182845905], [7.38905609893065, 20.0855369231877]]) + assert.throws(() => exp([0, 1, 2, 3]), /Function 'exp' doesn't apply/) + approx.deepEqual(math.map([0, 1, 2, 3], exp), [1, 2.71828182845905, 7.38905609893065, 20.0855369231877]) + approx.deepEqual(math.map([[0, 1], [2, 3]], exp), [[1, 2.71828182845905], [7.38905609893065, 20.0855369231877]]) // dense matrix - approx.deepEqual(exp(matrix([0, 1, 2, 3])), matrix([1, 2.71828182845905, 7.38905609893065, 20.0855369231877])) - approx.deepEqual(exp(matrix([[0, 1], [2, 3]])), matrix([[1, 2.71828182845905], [7.38905609893065, 20.0855369231877]])) + assert.throws(() => exp(matrix([0, 1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([0, 1, 2, 3]), exp), matrix([1, 2.71828182845905, 7.38905609893065, 20.0855369231877])) + approx.deepEqual(math.map(matrix([[0, 1], [2, 3]]), exp), matrix([[1, 2.71828182845905], [7.38905609893065, 20.0855369231877]])) // sparse matrix, TODO: it should return a dense matrix - approx.deepEqual(exp(sparse([[0, 1], [2, 3]])), sparse([[1, 2.71828182845905], [7.38905609893065, 20.0855369231877]])) + approx.deepEqual(math.map(sparse([[0, 1], [2, 3]]), exp), sparse([[1, 2.71828182845905], [7.38905609893065, 20.0855369231877]])) }) it('should LaTeX exp', function () { diff --git a/test/unit-tests/function/arithmetic/expm1.test.js b/test/unit-tests/function/arithmetic/expm1.test.js index 702dabac84..ab6b975d6b 100644 --- a/test/unit-tests/function/arithmetic/expm1.test.js +++ b/test/unit-tests/function/arithmetic/expm1.test.js @@ -74,14 +74,17 @@ describe('expm1', function () { assert.throws(function () { expm1('text') }) }) - it('should exponentiate matrices, arrays and ranges correctly', function () { + it('should not operate on matrices, arrays and ranges', function () { // array - approx.deepEqual(expm1([0, 1, 2, 3]), [0, 1.71828182845905, 6.38905609893065, 19.0855369231877]) - approx.deepEqual(expm1([[0, 1], [2, 3]]), [[0, 1.71828182845905], [6.38905609893065, 19.0855369231877]]) + assert.throws(() => expm1([0, 1, 2, 3]), /Function 'expm1' doesn't apply to matrices/) + approx.deepEqual(math.map([0, 1, 2, 3], expm1), [0, 1.71828182845905, 6.38905609893065, 19.0855369231877]) + approx.deepEqual(math.map([[0, 1], [2, 3]], expm1), [[0, 1.71828182845905], [6.38905609893065, 19.0855369231877]]) // dense matrix - approx.deepEqual(expm1(matrix([0, 1, 2, 3])), matrix([0, 1.71828182845905, 6.38905609893065, 19.0855369231877])) - approx.deepEqual(expm1(matrix([[0, 1], [2, 3]])), matrix([[0, 1.71828182845905], [6.38905609893065, 19.0855369231877]])) - approx.deepEqual(expm1(sparse([[0, 1], [2, 3]])), sparse([[0, 1.71828182845905], [6.38905609893065, 19.0855369231877]])) + assert.throws(() => expm1(matrix([0, 1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([0, 1, 2, 3]), expm1), matrix([0, 1.71828182845905, 6.38905609893065, 19.0855369231877])) + approx.deepEqual(math.map(matrix([[0, 1], [2, 3]]), expm1), matrix([[0, 1.71828182845905], [6.38905609893065, 19.0855369231877]])) + // sparse matrix + approx.deepEqual(math.map(sparse([[0, 1], [2, 3]]), expm1), sparse([[0, 1.71828182845905], [6.38905609893065, 19.0855369231877]])) }) it('should LaTeX expm1', function () { diff --git a/test/unit-tests/function/arithmetic/log.test.js b/test/unit-tests/function/arithmetic/log.test.js index 975f66aa3d..9219501e78 100644 --- a/test/unit-tests/function/arithmetic/log.test.js +++ b/test/unit-tests/function/arithmetic/log.test.js @@ -112,11 +112,12 @@ describe('log', function () { assert.throws(function () { log('text') }) }) - it('should return the log of each element of a matrix', function () { + it('should not operate on a matrix', function () { const res = [0, 0.693147180559945, 1.098612288668110, 1.386294361119891] - approx.deepEqual(log([1, 2, 3, 4]), res) - approx.deepEqual(log(matrix([1, 2, 3, 4])), matrix(res)) - approx.deepEqual(log(matrix([[1, 2], [3, 4]])), + assert.throws(() => log([1, 2, 3, 4]), TypeError) + approx.deepEqual(math.map([1, 2, 3, 4], x => log(x)), res) + approx.deepEqual(math.map(matrix([1, 2, 3, 4]), x => log(x)), matrix(res)) + approx.deepEqual(math.map(matrix([[1, 2], [3, 4]]), x => log(x)), matrix([[0, 0.693147180559945], [1.098612288668110, 1.386294361119891]])) }) diff --git a/test/unit-tests/function/arithmetic/norm.test.js b/test/unit-tests/function/arithmetic/norm.test.js index ee8dc32da9..2500b9bdc6 100644 --- a/test/unit-tests/function/arithmetic/norm.test.js +++ b/test/unit-tests/function/arithmetic/norm.test.js @@ -6,9 +6,7 @@ import math from '../../../../src/defaultInstance.js' describe('norm', function () { it('should return the absolute value of a boolean', function () { assert.strictEqual(math.norm(true), 1) - assert.strictEqual(math.norm(true, 10), 1) assert.strictEqual(math.norm(false), 0) - assert.strictEqual(math.norm(false, 10), 0) }) it('should return the absolute value of a number', function () { @@ -16,14 +14,13 @@ describe('norm', function () { assert.strictEqual(math.norm(-3.5), 3.5) assert.strictEqual(math.norm(100), 100) assert.strictEqual(math.norm(0), 0) - assert.strictEqual(math.norm(100, 10), 100) }) it('should return the absolute value of a big number', function () { assert.deepStrictEqual(math.norm(math.bignumber(-2.3)), math.bignumber(2.3)) assert.deepStrictEqual(math.norm(math.bignumber('5e500')), math.bignumber('5e500')) assert.deepStrictEqual(math.norm(math.bignumber('-5e500')), math.bignumber('5e500')) - assert.deepStrictEqual(math.norm(math.bignumber(-2.3), 'fro'), math.bignumber(2.3)) + assert.deepStrictEqual(math.norm(math.bignumber(-2.3)), math.bignumber(2.3)) assert.deepStrictEqual(math.norm([math.bignumber(-2.3)], 'fro'), math.bignumber(2.3)) assert.deepStrictEqual(math.norm([[math.bignumber(-2.3)]], 'fro'), math.bignumber(2.3)) }) diff --git a/test/unit-tests/function/arithmetic/sqrt.test.js b/test/unit-tests/function/arithmetic/sqrt.test.js index 6d206693aa..c6e3ba0e18 100644 --- a/test/unit-tests/function/arithmetic/sqrt.test.js +++ b/test/unit-tests/function/arithmetic/sqrt.test.js @@ -80,10 +80,11 @@ describe('sqrt', function () { }) }) - it('should return the square root of each element of a matrix', function () { - assert.deepStrictEqual(sqrt([4, 9, 16, 25]), [2, 3, 4, 5]) - assert.deepStrictEqual(sqrt([[4, 9], [16, 25]]), [[2, 3], [4, 5]]) - assert.deepStrictEqual(sqrt(math.matrix([[4, 9], [16, 25]])), math.matrix([[2, 3], [4, 5]])) + it('should not operate on a matrix', function () { + assert.throws(() => sqrt([4, 9, 16, 25]), TypeError) + assert.deepStrictEqual(math.map([4, 9, 16, 25], sqrt), [2, 3, 4, 5]) + assert.deepStrictEqual(math.map([[4, 9], [16, 25]], sqrt), [[2, 3], [4, 5]]) + assert.deepStrictEqual(math.map(math.matrix([[4, 9], [16, 25]]), sqrt), math.matrix([[2, 3], [4, 5]])) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/arithmetic/square.test.js b/test/unit-tests/function/arithmetic/square.test.js index 6340b2c6fb..f3b34c50fb 100644 --- a/test/unit-tests/function/arithmetic/square.test.js +++ b/test/unit-tests/function/arithmetic/square.test.js @@ -57,10 +57,11 @@ describe('square', function () { assert.throws(function () { square('text') }) }) - it('should return the square of each element in a matrix', function () { - assert.deepStrictEqual(square([2, 3, 4, 5]), [4, 9, 16, 25]) - assert.deepStrictEqual(square(matrix([2, 3, 4, 5])), matrix([4, 9, 16, 25])) - assert.deepStrictEqual(square(matrix([[1, 2], [3, 4]])), matrix([[1, 4], [9, 16]])) + it('should not operate on a matrix', function () { + assert.throws(() => square([2, 3, 4, 5]), TypeError) + assert.deepStrictEqual(math.map([2, 3, 4, 5], square), [4, 9, 16, 25]) + assert.deepStrictEqual(math.map(matrix([2, 3, 4, 5]), square), matrix([4, 9, 16, 25])) + assert.deepStrictEqual(math.map(matrix([[1, 2], [3, 4]]), square), matrix([[1, 4], [9, 16]])) }) it('should LaTeX square', function () { diff --git a/test/unit-tests/function/logical/not.test.js b/test/unit-tests/function/logical/not.test.js index dce3319ce6..c946a56263 100644 --- a/test/unit-tests/function/logical/not.test.js +++ b/test/unit-tests/function/logical/not.test.js @@ -72,13 +72,16 @@ describe('not', function () { assert.deepStrictEqual(not(matrix([])), matrix([])) }) + it('should not null', function () { + assert.strictEqual(not(null), true) + }) + it('should throw an error in case of invalid number of arguments', function () { assert.throws(function () { not() }, /TypeError: Too few arguments/) assert.throws(function () { not(1, 2) }, /TypeError: Too many arguments/) }) it('should throw an error in case of invalid type if arguments', function () { - assert.throws(function () { not(null) }, /TypeError: Unexpected type of argument/) assert.throws(function () { not(new Date()) }, /TypeError: Unexpected type of argument/) assert.throws(function () { not({}) }, /TypeError: Unexpected type of argument/) }) diff --git a/test/unit-tests/function/matrix/count.test.js b/test/unit-tests/function/matrix/count.test.js index d4954b63ce..2e09487b4f 100644 --- a/test/unit-tests/function/matrix/count.test.js +++ b/test/unit-tests/function/matrix/count.test.js @@ -31,7 +31,7 @@ describe('count', function () { it('should throw an error if called with an invalid number of arguments', function () { assert.throws(function () { count() }, /TypeError: Too few arguments/) - assert.throws(function () { count(1, 2) }, /TypeError: Too many arguments/) + assert.throws(function () { count([1], 2) }, /TypeError: Too many arguments/) }) it('should throw an error if called with invalid type of arguments', function () { diff --git a/test/unit-tests/function/matrix/diag.test.js b/test/unit-tests/function/matrix/diag.test.js index 68a514475b..30102d1d98 100644 --- a/test/unit-tests/function/matrix/diag.test.js +++ b/test/unit-tests/function/matrix/diag.test.js @@ -108,7 +108,7 @@ describe('diag', function () { it('should throw an error in case of wrong number of arguments', function () { assert.throws(function () { math.diag() }, /TypeError: Too few arguments/) - assert.throws(function () { math.diag([], 2, 3, 4) }, /TypeError: Too many arguments/) + assert.throws(function () { math.diag([], 2, 'dense', 4) }, /TypeError: Too many arguments/) }) it('should throw an error in case of invalid type of arguments', function () { diff --git a/test/unit-tests/function/matrix/diff.test.js b/test/unit-tests/function/matrix/diff.test.js index 27ee3c9c64..2a79d24cb9 100644 --- a/test/unit-tests/function/matrix/diff.test.js +++ b/test/unit-tests/function/matrix/diff.test.js @@ -21,18 +21,24 @@ const largeTestArrayDimension2 = [[[[1, 1, 1], [1, 1, 1]], [[-1, 1, 3], [3, 1, - const largeTestArrayDimension3 = [[[[1, 1], [1, 1], [1, 1]], [[-1, -1], [1, 1], [-1, -1]], [[-3, -1], [-3, -1], [-3, -1]]], [[[4, 333], [12, -12], [111, -222]], [[1, 1], [2, 2], [1, 1]], [[-11, -11], [0, -31], [1, 1]]], [[[63, -61], [32, 27], [-36, -28]], [[-10, -1], [2, 1], [5, -4]], [[-30, -1], [-3, -1], [-99, -1]]]] describe('diff', function () { - it('should return original array/matrix for less than 2 elements, with and without specified dimension', function () { + it('should return an empty array/matrix for less than 2 elements, with and without specified dimension', function () { // With Dim = 0 specified assert.deepStrictEqual(diff([], 0), []) assert.deepStrictEqual(diff(matrix([]), 0), matrix([])) - assert.deepStrictEqual(diff([2], 0), [2]) - assert.deepStrictEqual(diff(matrix([2]), 0), matrix([2])) + assert.deepStrictEqual(diff([2], 0), []) + assert.deepStrictEqual(diff(matrix([2]), 0), matrix([])) // Without Dim = 0 specified assert.deepStrictEqual(diff([]), []) assert.deepStrictEqual(diff(matrix([])), matrix([])) - assert.deepStrictEqual(diff([2]), [2]) - assert.deepStrictEqual(diff(matrix([2])), matrix([2])) + assert.deepStrictEqual(diff([2]), []) + assert.deepStrictEqual(diff(matrix([2])), matrix([])) + + // Two-dimensional: + assert.deepStrictEqual(diff([[1, 3, 6, 10, 15]]), []) + assert.deepStrictEqual(diff([[1, 3, 6, 10, 15]], 1), [[2, 3, 4, 5]]) + assert.deepStrictEqual(diff([[1], [3], [6], [10], [15]]), [[2], [3], [4], [5]]) + assert.deepStrictEqual(diff([[1], [3], [6], [10], [15]], 1), [[], [], [], [], []]) }) it('should return difference between elements of a 1-dimensional array, with and without specified dimension', function () { diff --git a/test/unit-tests/function/matrix/sqrtm.test.js b/test/unit-tests/function/matrix/sqrtm.test.js index 0933367de2..74d7a977e2 100644 --- a/test/unit-tests/function/matrix/sqrtm.test.js +++ b/test/unit-tests/function/matrix/sqrtm.test.js @@ -70,9 +70,9 @@ describe('sqrtm', function () { assert.strictEqual(math.typeOf(math.sqrtm(AA)), 'Array') assert.strictEqual(math.typeOf(math.sqrtm(BB)), 'Array') - assert.strictEqual(math.typeOf(math.sqrtm(math.matrix(A))), 'Matrix') - assert.strictEqual(math.typeOf(math.sqrtm(math.matrix(B))), 'Matrix') - assert.strictEqual(math.typeOf(math.sqrtm(math.matrix(AA))), 'Matrix') - assert.strictEqual(math.typeOf(math.sqrtm(math.matrix(BB))), 'Matrix') + assert.strictEqual(math.typeOf(math.sqrtm(math.matrix(A))), 'DenseMatrix') + assert.strictEqual(math.typeOf(math.sqrtm(math.matrix(B))), 'DenseMatrix') + assert.strictEqual(math.typeOf(math.sqrtm(math.matrix(AA))), 'DenseMatrix') + assert.strictEqual(math.typeOf(math.sqrtm(math.matrix(BB))), 'DenseMatrix') }) }) diff --git a/test/unit-tests/function/matrix/transpose.test.js b/test/unit-tests/function/matrix/transpose.test.js index 9277631776..9b7a384e16 100644 --- a/test/unit-tests/function/matrix/transpose.test.js +++ b/test/unit-tests/function/matrix/transpose.test.js @@ -27,7 +27,7 @@ describe('transpose', function () { }) assert.throws(function () { transpose([[[1], [2]], [[3], [4]]]) // size [2,2,1] - }) + }, /RangeError: Matrix.*2, 2, 1/) }) it('should throw an error if called with an invalid number of arguments', function () { diff --git a/test/unit-tests/function/probability/gamma.test.js b/test/unit-tests/function/probability/gamma.test.js index 6c3c74a0a4..239ed8a78e 100644 --- a/test/unit-tests/function/probability/gamma.test.js +++ b/test/unit-tests/function/probability/gamma.test.js @@ -145,12 +145,14 @@ describe('gamma', function () { assert.strictEqual(gamma(false), Infinity) }) - it('should calculate the gamma of each element in a matrix', function () { - assert.deepStrictEqual(gamma(math.matrix([0, 1, 2, 3, 4, 5])), math.matrix([Infinity, 1, 1, 2, 6, 24])) + it('should not operate on a matrix', function () { + assert.throws(() => gamma(math.matrix([0, 1, 2, 3, 4, 5])), /Function 'gamma' doesn't apply to matrices/) + assert.deepStrictEqual(math.map(math.matrix([0, 1, 2, 3, 4, 5]), gamma), math.matrix([Infinity, 1, 1, 2, 6, 24])) }) - it('should calculate the gamma of each element in an array', function () { - assert.deepStrictEqual(gamma([0, 1, 2, 3, 4, 5]), [Infinity, 1, 1, 2, 6, 24]) + it('should not operate on an array', function () { + assert.throws(() => gamma([0, 1, 2, 3, 4, 5]), TypeError) + assert.deepStrictEqual(math.map([0, 1, 2, 3, 4, 5], gamma), [Infinity, 1, 1, 2, 6, 24]) }) it('should throw en error if called with invalid number of arguments', function () { diff --git a/test/unit-tests/function/set/setCartesian.test.js b/test/unit-tests/function/set/setCartesian.test.js index 619591cac9..260074b711 100644 --- a/test/unit-tests/function/set/setCartesian.test.js +++ b/test/unit-tests/function/set/setCartesian.test.js @@ -22,7 +22,10 @@ describe('setCartesian', function () { it('should return the same type of output as the inputs', function () { assert.strictEqual(math.typeOf(math.setCartesian([1, 2, 3], [3, 4, 5])), 'Array') - assert.strictEqual(math.typeOf(math.setCartesian(math.matrix([1, 2, 3]), math.matrix([3, 4, 5]))), 'Matrix') + assert.strictEqual( + math.typeOf( + math.setCartesian(math.matrix([1, 2, 3]), math.matrix([3, 4, 5]))), + 'DenseMatrix') }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/set/setDifference.test.js b/test/unit-tests/function/set/setDifference.test.js index 80e3cd96fe..98d8b5f3eb 100644 --- a/test/unit-tests/function/set/setDifference.test.js +++ b/test/unit-tests/function/set/setDifference.test.js @@ -23,7 +23,10 @@ describe('setDifference', function () { it('should return the same type of output as the inputs', function () { assert.strictEqual(math.typeOf(math.setDifference([1, 2, 3], [3, 4, 5])), 'Array') - assert.strictEqual(math.typeOf(math.setDifference(math.matrix([1, 2, 3]), math.matrix([3, 4, 5]))), 'Matrix') + assert.strictEqual( + math.typeOf( + math.setDifference(math.matrix([1, 2, 3]), math.matrix([3, 4, 5]))), + 'DenseMatrix') }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/set/setDistinct.test.js b/test/unit-tests/function/set/setDistinct.test.js index 93e1b5bf27..0688df062b 100644 --- a/test/unit-tests/function/set/setDistinct.test.js +++ b/test/unit-tests/function/set/setDistinct.test.js @@ -17,7 +17,9 @@ describe('setDistinct', function () { it('should return the same type of output as the inputs', function () { assert.strictEqual(math.typeOf(math.setDistinct([1, 2, 3])), 'Array') - assert.strictEqual(math.typeOf(math.setDistinct(math.matrix([1, 2, 3]))), 'Matrix') + assert.strictEqual( + math.typeOf(math.setDistinct(math.matrix([1, 2, 3]))), + 'DenseMatrix') }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/set/setIntersect.test.js b/test/unit-tests/function/set/setIntersect.test.js index 5ec2f87b47..84759ba78d 100644 --- a/test/unit-tests/function/set/setIntersect.test.js +++ b/test/unit-tests/function/set/setIntersect.test.js @@ -21,7 +21,10 @@ describe('setIntersect', function () { it('should return the same type of output as the inputs', function () { assert.strictEqual(math.typeOf(math.setIntersect([1, 2, 3], [3, 4, 5])), 'Array') - assert.strictEqual(math.typeOf(math.setIntersect(math.matrix([1, 2, 3]), math.matrix([3, 4, 5]))), 'Matrix') + assert.strictEqual( + math.typeOf( + math.setIntersect(math.matrix([1, 2, 3]), math.matrix([3, 4, 5]))), + 'DenseMatrix') }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/set/setSymDifference.test.js b/test/unit-tests/function/set/setSymDifference.test.js index e79e90c916..eb22dd0a8c 100644 --- a/test/unit-tests/function/set/setSymDifference.test.js +++ b/test/unit-tests/function/set/setSymDifference.test.js @@ -18,7 +18,10 @@ describe('setSymDifference', function () { it('should return the same type of output as the inputs', function () { assert.strictEqual(math.typeOf(math.setSymDifference([1, 2, 3], [3, 4, 5])), 'Array') - assert.strictEqual(math.typeOf(math.setSymDifference(math.matrix([1, 2, 3]), math.matrix([3, 4, 5]))), 'Matrix') + assert.strictEqual( + math.typeOf( + math.setSymDifference(math.matrix([1, 2, 3]), math.matrix([3, 4, 5]))), + 'DenseMatrix') }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/set/setUnion.test.js b/test/unit-tests/function/set/setUnion.test.js index 9470cf4443..b8bf6f5c68 100644 --- a/test/unit-tests/function/set/setUnion.test.js +++ b/test/unit-tests/function/set/setUnion.test.js @@ -17,7 +17,9 @@ describe('setUnion', function () { it('should return the same type of output as the inputs', function () { assert.strictEqual(math.typeOf(math.setUnion([1, 2, 3], [3, 4, 5])), 'Array') - assert.strictEqual(math.typeOf(math.setUnion(math.matrix([1, 2, 3]), math.matrix([3, 4, 5]))), 'Matrix') + assert.strictEqual( + math.typeOf(math.setUnion(math.matrix([1, 2, 3]), math.matrix([3, 4, 5]))), + 'DenseMatrix') }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/acos.test.js b/test/unit-tests/function/trigonometry/acos.test.js index d1e79fced3..422cb7974e 100644 --- a/test/unit-tests/function/trigonometry/acos.test.js +++ b/test/unit-tests/function/trigonometry/acos.test.js @@ -93,12 +93,13 @@ describe('acos', function () { assert.throws(function () { acos('string') }) }) - it('should calculate the arccos element-wise for arrays and matrices', function () { + it('should not operate on arrays and matrices', function () { + assert.throws(() => acos([1, 2, 3]), TypeError) // note: the results of acos(2) and acos(3) differs in octave // the next tests are verified with mathematica const acos123 = [0, complex(0, 1.316957896924817), complex(0, 1.762747174039086)] - approx.deepEqual(acos([1, 2, 3]), acos123) - approx.deepEqual(acos(matrix([1, 2, 3])), matrix(acos123)) + approx.deepEqual(math.map([1, 2, 3], acos), acos123) + approx.deepEqual(math.map(matrix([1, 2, 3]), acos), matrix(acos123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/acosh.test.js b/test/unit-tests/function/trigonometry/acosh.test.js index 48df0e807b..6fc4fa5d42 100644 --- a/test/unit-tests/function/trigonometry/acosh.test.js +++ b/test/unit-tests/function/trigonometry/acosh.test.js @@ -95,10 +95,12 @@ describe('acosh', function () { assert.throws(function () { acosh('string') }) }) - it('should calculate the arccos element-wise for arrays and matrices', function () { + it('should not operate on arrays and matrices', function () { + assert.throws(() => acosh([1, 2, 3]), TypeError) + assert.throws(() => acosh(matrix([1, 2, 3])), TypeError) const acosh123 = [0, 1.3169578969248167, 1.7627471740390860504] - approx.deepEqual(acosh([1, 2, 3]), acosh123) - approx.deepEqual(acosh(matrix([1, 2, 3])), matrix(acosh123)) + approx.deepEqual(math.map([1, 2, 3], acosh), acosh123) + approx.deepEqual(math.map(matrix([1, 2, 3]), acosh), matrix(acosh123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/acot.test.js b/test/unit-tests/function/trigonometry/acot.test.js index 1ab2ffdb58..7656fd5c74 100644 --- a/test/unit-tests/function/trigonometry/acot.test.js +++ b/test/unit-tests/function/trigonometry/acot.test.js @@ -97,11 +97,13 @@ describe('acot', function () { assert.throws(function () { acot('string') }) }) - it('should calculate the arccot element-wise for arrays and matrices', function () { + it('should not operate on arrays and matrices', function () { + assert.throws(() => acot([1, 2, 3]), TypeError) + assert.throws(() => acot(matrix([1, 2, 3])), TypeError) // matrix, array, range const acot123 = [pi / 4, 0.4636476090008, 0.3217505543966] - approx.deepEqual(acot([1, 2, 3]), acot123) - approx.deepEqual(acot(matrix([1, 2, 3])), matrix(acot123)) + approx.deepEqual(math.map([1, 2, 3], acot), acot123) + approx.deepEqual(math.map(matrix([1, 2, 3]), acot), matrix(acot123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/acoth.test.js b/test/unit-tests/function/trigonometry/acoth.test.js index cf3adea646..35e290b5af 100644 --- a/test/unit-tests/function/trigonometry/acoth.test.js +++ b/test/unit-tests/function/trigonometry/acoth.test.js @@ -98,10 +98,12 @@ describe('acoth', function () { assert.throws(function () { acoth('string') }) }) - it('should calculate the arccot element-wise for arrays and matrices', function () { + it('should not operate on arrays and matrices', function () { + assert.throws(() => acoth([1, 2, 3]), TypeError) + assert.throws(() => acoth(matrix([1, 2, 3])), TypeError) const acoth123 = [Infinity, 0.54930614433405, 0.34657359027997] - approx.deepEqual(acoth([1, 2, 3]), acoth123) - approx.deepEqual(acoth(matrix([1, 2, 3])), matrix(acoth123)) + approx.deepEqual(math.map([1, 2, 3], acoth), acoth123) + approx.deepEqual(math.map(matrix([1, 2, 3]), acoth), matrix(acoth123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/acsc.test.js b/test/unit-tests/function/trigonometry/acsc.test.js index 1196fea346..0ee7f105f6 100644 --- a/test/unit-tests/function/trigonometry/acsc.test.js +++ b/test/unit-tests/function/trigonometry/acsc.test.js @@ -122,10 +122,12 @@ describe('acsc', function () { assert.throws(function () { acsc('string') }) }) - it('should calculate the arccsc element-wise for arrays and matrices', function () { + it('should not operate on arrays and matrices', function () { + assert.throws(() => acsc([1, 2, 3]), TypeError) + assert.throws(() => acsc(matrix([1, 2, 3])), TypeError) const acsc123 = [pi / 2, pi / 6, 0.339836909454] - approx.deepEqual(acsc([1, 2, 3]), acsc123) - approx.deepEqual(acsc(matrix([1, 2, 3])), matrix(acsc123)) + approx.deepEqual(math.map([1, 2, 3], acsc), acsc123) + approx.deepEqual(math.map(matrix([1, 2, 3]), acsc), matrix(acsc123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/acsch.test.js b/test/unit-tests/function/trigonometry/acsch.test.js index 906ba938c2..507a4ce3fb 100644 --- a/test/unit-tests/function/trigonometry/acsch.test.js +++ b/test/unit-tests/function/trigonometry/acsch.test.js @@ -79,10 +79,12 @@ describe('acsch', function () { assert.throws(function () { acsch('string') }) }) - it('should calculate the arccsc element-wise for arrays and matrices', function () { + it('should not operate on arrays and matrices', function () { + assert.throws(() => acsch([1, 2, 3]), TypeError) + assert.throws(() => acsch(matrix([1, 2, 3])), TypeError) const acsch123 = [0.881373587019543025, 0.481211825059603447, 0.32745015023725844] - approx.deepEqual(acsch([1, 2, 3]), acsch123) - approx.deepEqual(acsch(matrix([1, 2, 3])), matrix(acsch123)) + approx.deepEqual(math.map([1, 2, 3], acsch), acsch123) + approx.deepEqual(math.map(matrix([1, 2, 3]), acsch), matrix(acsch123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/asec.test.js b/test/unit-tests/function/trigonometry/asec.test.js index defdf33ab5..660d4242bc 100644 --- a/test/unit-tests/function/trigonometry/asec.test.js +++ b/test/unit-tests/function/trigonometry/asec.test.js @@ -103,10 +103,12 @@ describe('asec', function () { assert.throws(function () { asec('string') }) }) - it('should calculate the arcsec element-wise for arrays and matrices', function () { + it('should not operate on arrays and matrices', function () { + assert.throws(() => asec([1, 2, 3]), TypeError) + assert.throws(() => asec(matrix([1, 2, 3])), TypeError) const asec123 = [0, pi / 3, 1.23095941734077468] - approx.deepEqual(asec([1, 2, 3]), asec123) - approx.deepEqual(asec(matrix([1, 2, 3])), matrix(asec123)) + approx.deepEqual(math.map([1, 2, 3], asec), asec123) + approx.deepEqual(math.map(matrix([1, 2, 3]), asec), matrix(asec123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/asech.test.js b/test/unit-tests/function/trigonometry/asech.test.js index 76a9fe82db..8192160f44 100644 --- a/test/unit-tests/function/trigonometry/asech.test.js +++ b/test/unit-tests/function/trigonometry/asech.test.js @@ -99,10 +99,12 @@ describe('asech', function () { assert.throws(function () { asech('string') }) }) - it('should calculate the arcsec element-wise for arrays and matrices', function () { + it('should not operate on arrays and matrices', function () { + assert.throws(() => asech([0, 1]), TypeError) + assert.throws(() => asech(matrix([0, 1])), TypeError) const asech01 = [Infinity, 0] - assert.deepStrictEqual(asech([0, 1]), asech01) - assert.deepStrictEqual(asech(matrix([0, 1])), matrix(asech01)) + assert.deepStrictEqual(math.map([0, 1], asech), asech01) + assert.deepStrictEqual(math.map(matrix([0, 1]), asech), matrix(asech01)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/asin.test.js b/test/unit-tests/function/trigonometry/asin.test.js index 2d6b54c218..84f0bdae47 100644 --- a/test/unit-tests/function/trigonometry/asin.test.js +++ b/test/unit-tests/function/trigonometry/asin.test.js @@ -116,15 +116,17 @@ describe('asin', function () { assert.throws(function () { asin('string') }) }) - it('should calculate the arcsin element-wise for arrays and matrices', function () { + it('should not operate on arrays and matrices', function () { + assert.throws(() => asin([1, 2, 3]), TypeError) + assert.throws(() => asin(matrix([1, 2, 3])), TypeError) // note: the results of asin(2) and asin(3) differs in octave // the next tests are verified with mathematica const asin123 = [ 1.57079632679490, complex(1.57079632679490, -1.31695789692482), complex(1.57079632679490, -1.76274717403909)] - approx.deepEqual(asin([1, 2, 3]), asin123) - approx.deepEqual(asin(matrix([1, 2, 3])), matrix(asin123)) + approx.deepEqual(math.map([1, 2, 3], asin), asin123) + approx.deepEqual(math.map(matrix([1, 2, 3]), asin), matrix(asin123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/asinh.test.js b/test/unit-tests/function/trigonometry/asinh.test.js index fd0a3a0c23..bf0b13ecec 100644 --- a/test/unit-tests/function/trigonometry/asinh.test.js +++ b/test/unit-tests/function/trigonometry/asinh.test.js @@ -80,10 +80,12 @@ describe('asinh', function () { assert.throws(function () { asinh('string') }) }) - it('should calculate the arcsin element-wise for arrays and matrices', function () { + it('should not operate on arrays and matrices', function () { + assert.throws(() => asinh([1, 2, 3]), TypeError) + assert.throws(() => asinh(matrix([1, 2, 3])), TypeError) const asinh123 = [0.881373587019543025, 1.4436354751788103, 1.8184464592320668] - approx.deepEqual(asinh([1, 2, 3]), asinh123) - approx.deepEqual(asinh(matrix([1, 2, 3])), matrix(asinh123)) + approx.deepEqual(math.map([1, 2, 3], asinh), asinh123) + approx.deepEqual(math.map(matrix([1, 2, 3]), asinh), matrix(asinh123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/atan.test.js b/test/unit-tests/function/trigonometry/atan.test.js index c64135aead..3e1d94b199 100644 --- a/test/unit-tests/function/trigonometry/atan.test.js +++ b/test/unit-tests/function/trigonometry/atan.test.js @@ -92,11 +92,13 @@ describe('atan', function () { assert.throws(function () { atan('string') }) }) - it('should calculate the arctan element-wise for arrays and matrices', function () { + it('should not operate on arrays and matrices', function () { + assert.throws(() => atan([1, 2, 3]), TypeError) + assert.throws(() => atan(matrix([1, 2, 3])), TypeError) // matrix, array, range const atan123 = [0.785398163397448, 1.107148717794090, 1.249045772398254] - approx.deepEqual(atan([1, 2, 3]), atan123) - approx.deepEqual(atan(matrix([1, 2, 3])), matrix(atan123)) + approx.deepEqual(math.map([1, 2, 3], atan), atan123) + approx.deepEqual(math.map(matrix([1, 2, 3]), atan), matrix(atan123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/atanh.test.js b/test/unit-tests/function/trigonometry/atanh.test.js index b4442eadde..2cd308b694 100644 --- a/test/unit-tests/function/trigonometry/atanh.test.js +++ b/test/unit-tests/function/trigonometry/atanh.test.js @@ -97,10 +97,12 @@ describe('atanh', function () { assert.throws(function () { atanh('string') }) }) - it('should calculate the arctan element-wise for arrays and matrices', function () { + it('should not operate on arrays and matrices', function () { + assert.throws(() => atanh([-1, 0, 1]), TypeError) + assert.throws(() => atanh(matrix([-1, 0, 1])), TypeError) const atanh101 = [-Infinity, 0, Infinity] - assert.deepStrictEqual(atanh([-1, 0, 1]), atanh101) - assert.deepStrictEqual(atanh(matrix([-1, 0, 1])), matrix(atanh101)) + assert.deepStrictEqual(math.map([-1, 0, 1], atanh), atanh101) + assert.deepStrictEqual(math.map(matrix([-1, 0, 1]), atanh), matrix(atanh101)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/cos.test.js b/test/unit-tests/function/trigonometry/cos.test.js index 8d75cb73f8..896e79911b 100644 --- a/test/unit-tests/function/trigonometry/cos.test.js +++ b/test/unit-tests/function/trigonometry/cos.test.js @@ -101,12 +101,14 @@ describe('cos', function () { const cos123 = [0.540302305868140, -0.41614683654714, -0.989992496600445] - it('should return the cos of each element of a matrix', function () { - approx.deepEqual(cos(matrix([1, 2, 3])), matrix(cos123)) + it('should not operate on a matrix', function () { + assert.throws(() => cos(matrix([1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([1, 2, 3]), cos), matrix(cos123)) }) - it('should return the cos of each element of an array', function () { - approx.deepEqual(cos([1, 2, 3]), cos123) + it('should not operate on an array', function () { + assert.throws(() => cos([1, 2, 3]), TypeError) + approx.deepEqual(math.map([1, 2, 3], cos), cos123) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/cosh.test.js b/test/unit-tests/function/trigonometry/cosh.test.js index 3c53fffe98..4d4a4fbdcd 100644 --- a/test/unit-tests/function/trigonometry/cosh.test.js +++ b/test/unit-tests/function/trigonometry/cosh.test.js @@ -52,14 +52,8 @@ describe('cosh', function () { approx.deepEqual(cosh(complex('2 + i')), complex(2.0327230070197, 3.0518977991518)) }) - it('should return the cosh of an angle', function () { - approx.equal(cosh(unit('90deg')), 2.5091784786581) - approx.equal(cosh(unit('-45deg')), 1.324609089252) - - assert(math.isBigNumber(cosh(unit(math.bignumber(90), 'deg')))) - approx.equal(cosh(unit(math.bignumber(90), 'deg')).toNumber(), 2.5091784786581) - - approx.deepEqual(cosh(math.unit(complex('2 + i'), 'rad')), complex(2.0327230070197, 3.0518977991518)) + it('should throw an error on an angle', function () { + assert.throws(() => cosh(unit('90deg')), TypeError) }) it('should throw an error if called with an invalid unit', function () { @@ -72,12 +66,14 @@ describe('cosh', function () { const cosh123 = [1.5430806348152, 3.7621956910836, 10.067661995778] - it('should return the cosh of each element of an array', function () { - approx.deepEqual(cosh([1, 2, 3]), cosh123) + it('should not operate on an array', function () { + assert.throws(() => cosh([1, 2, 3]), TypeError) + approx.deepEqual(math.map([1, 2, 3], cosh), cosh123) }) - it('should return the cosh of each element of a matrix', function () { - approx.deepEqual(cosh(matrix([1, 2, 3])), matrix(cosh123)) + it('should not operate on a matrix', function () { + assert.throws(() => cosh(matrix([1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([1, 2, 3]), cosh), matrix(cosh123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/cot.test.js b/test/unit-tests/function/trigonometry/cot.test.js index ed347e355c..32317966aa 100644 --- a/test/unit-tests/function/trigonometry/cot.test.js +++ b/test/unit-tests/function/trigonometry/cot.test.js @@ -78,12 +78,14 @@ describe('cot', function () { const cot123 = [0.642092615934331, -0.457657554360286, -7.015252551434534] - it('should return the cotan of each element of an array', function () { - approx.deepEqual(cot([1, 2, 3]), cot123) + it('should not operate on an array', function () { + assert.throws(() => cot([1, 2, 3]), TypeError) + approx.deepEqual(math.map([1, 2, 3], cot), cot123) }) - it('should return the cotan of each element of a matrix', function () { - approx.deepEqual(cot(matrix([1, 2, 3])), matrix(cot123)) + it('should not operate on a matrix', function () { + assert.throws(() => cot(matrix([1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([1, 2, 3]), cot), matrix(cot123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/coth.test.js b/test/unit-tests/function/trigonometry/coth.test.js index 7b8e887101..987bd5d2b2 100644 --- a/test/unit-tests/function/trigonometry/coth.test.js +++ b/test/unit-tests/function/trigonometry/coth.test.js @@ -41,14 +41,8 @@ describe('coth', function () { approx.deepEqual(coth(complex('2 + i')), complex(0.98432922645819, -0.032797755533753)) }) - it('should return the coth of an angle', function () { - approx.equal(coth(unit('90deg')), 1.0903314107274) - approx.equal(coth(unit('-45deg')), -1.5248686188221) - - assert(math.isBigNumber(coth(unit(math.bignumber(90), 'deg')))) - approx.equal(coth(unit(math.bignumber(90), 'deg')).toNumber(), 1.0903314107274) - - approx.deepEqual(coth(math.unit(complex('2 + i'), 'rad')), complex(0.98432922645819, -0.032797755533753)) + it('should throw an error on an angle', function () { + assert.throws(() => coth(unit('90deg')), TypeError) }) it('should throw an error if called with an invalid unit', function () { @@ -61,12 +55,14 @@ describe('coth', function () { const coth123 = [1.3130352854993, 1.0373147207275, 1.0049698233137] - it('should return the coth of each element of an array', function () { - approx.deepEqual(coth([1, 2, 3]), coth123) + it('should not operate on an array', function () { + assert.throws(() => coth([1, 2, 3]), TypeError) + approx.deepEqual(math.map([1, 2, 3], coth), coth123) }) - it('should return the coth of each element of a matrix', function () { - approx.deepEqual(coth(matrix([1, 2, 3])), matrix(coth123)) + it('should not operate on a matrix', function () { + assert.throws(() => coth(matrix([1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([1, 2, 3]), coth), matrix(coth123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/csc.test.js b/test/unit-tests/function/trigonometry/csc.test.js index a5e3dbafb1..02ebaeace0 100644 --- a/test/unit-tests/function/trigonometry/csc.test.js +++ b/test/unit-tests/function/trigonometry/csc.test.js @@ -73,12 +73,14 @@ describe('csc', function () { const csc123 = [1.18839510577812, 1.09975017029462, 7.08616739573719] - it('should return the cosecant of each element of an array', function () { - approx.deepEqual(csc([1, 2, 3]), csc123) + it('should not operate on an array', function () { + assert.throws(() => csc([1, 2, 3]), TypeError) + approx.deepEqual(math.map([1, 2, 3], csc), csc123) }) - it('should return the cosecant of each element of a matrix', function () { - approx.deepEqual(csc(matrix([1, 2, 3])), matrix(csc123)) + it('should not operate on a matrix', function () { + assert.throws(() => csc(matrix([1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([1, 2, 3]), csc), matrix(csc123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/csch.test.js b/test/unit-tests/function/trigonometry/csch.test.js index 170e787c8f..0b07f5c887 100644 --- a/test/unit-tests/function/trigonometry/csch.test.js +++ b/test/unit-tests/function/trigonometry/csch.test.js @@ -44,14 +44,8 @@ describe('csch', function () { approx.deepEqual(csch(complex('2 + i')), complex(0.14136302161241, -0.22837506559969)) }) - it('should return the csch of an angle', function () { - approx.equal(csch(unit('90deg')), 0.4345372080947) - approx.equal(csch(unit('-45deg')), -1.1511838709208) - - assert(math.isBigNumber(csch(unit(math.bignumber(90), 'deg')))) - approx.equal(csch(unit(math.bignumber(90), 'deg')).toNumber(), 0.4345372080947) - - approx.deepEqual(csch(unit(complex('2 + i'), 'rad')), complex(0.14136302161241, -0.22837506559969)) + it('should throw an error on an angle', function () { + assert.throws(() => csch(unit('90deg')), TypeError) }) it('should throw an error if called with an invalid unit', function () { @@ -64,12 +58,14 @@ describe('csch', function () { const csch123 = [0.85091812823932, 0.27572056477178, 0.099821569668823] - it('should return the csch of each element of an array', function () { - approx.deepEqual(csch([1, 2, 3]), csch123) + it('should not operate on an array', function () { + assert.throws(() => csch([1, 2, 3]), TypeError) + approx.deepEqual(math.map([1, 2, 3], csch), csch123) }) - it('should return the csch of each element of a matrix', function () { - approx.deepEqual(csch(matrix([1, 2, 3])), matrix(csch123)) + it('should not operate on a matrix', function () { + assert.throws(() => csch(matrix([1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([1, 2, 3]), csch), matrix(csch123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/sec.test.js b/test/unit-tests/function/trigonometry/sec.test.js index 79b70c1efc..b8e49fa55c 100644 --- a/test/unit-tests/function/trigonometry/sec.test.js +++ b/test/unit-tests/function/trigonometry/sec.test.js @@ -89,12 +89,14 @@ describe('sec', function () { const sec123 = [1.85081571768093, -2.40299796172238, -1.01010866590799] - it('should return the secant of each element of an array', function () { - approx.deepEqual(sec([1, 2, 3]), sec123) + it('should not operate on an array', function () { + assert.throws(() => sec([1, 2, 3]), TypeError) + approx.deepEqual(math.map([1, 2, 3], sec), sec123) }) - it('should return the secant of each element of a matrix', function () { - approx.deepEqual(sec(matrix([1, 2, 3])), matrix(sec123)) + it('should not operate on a matrix', function () { + assert.throws(() => sec(matrix([1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([1, 2, 3]), sec), matrix(sec123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/sech.test.js b/test/unit-tests/function/trigonometry/sech.test.js index fe25a1e9ec..48b564acbd 100644 --- a/test/unit-tests/function/trigonometry/sech.test.js +++ b/test/unit-tests/function/trigonometry/sech.test.js @@ -42,14 +42,8 @@ describe('sech', function () { approx.deepEqual(sech(complex('2 + i')), complex(0.15117629826558, -0.22697367539372)) }) - it('should return the sech of an angle', function () { - approx.equal(sech(unit('90deg')), 0.39853681533839) - approx.equal(sech(unit('-45deg')), 0.75493970871413) - - assert(math.isBigNumber(sech(unit(math.bignumber(90), 'deg')))) - approx.equal(sech(unit(math.bignumber(90), 'deg')).toNumber(), 0.39853681533839) - - approx.deepEqual(sech(unit(complex('2 + i'), 'rad')), complex(0.15117629826558, -0.22697367539372)) + it('should throw an error on an angle', function () { + assert.throws(() => sech(unit('90deg')), TypeError) }) it('should throw an error if called with an invalid unit', function () { @@ -62,12 +56,14 @@ describe('sech', function () { const sech123 = [0.64805427366389, 0.26580222883408, 0.099327927419433] - it('should return the sech of each element of an array', function () { - approx.deepEqual(sech([1, 2, 3]), sech123) + it('should not operate on an array', function () { + assert.throws(() => sech([1, 2, 3]), TypeError) + approx.deepEqual(math.map([1, 2, 3], sech), sech123) }) - it('should return the sech of each element of a matrix', function () { - approx.deepEqual(sech(matrix([1, 2, 3])), matrix(sech123)) + it('should not operate on a matrix', function () { + assert.throws(() => sech(matrix([1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([1, 2, 3]), sech), matrix(sech123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/sin.test.js b/test/unit-tests/function/trigonometry/sin.test.js index cf402b0b81..50e577043f 100644 --- a/test/unit-tests/function/trigonometry/sin.test.js +++ b/test/unit-tests/function/trigonometry/sin.test.js @@ -104,12 +104,14 @@ describe('sin', function () { const sin123 = [0.84147098480789, 0.909297426825682, 0.141120008059867] - it('should return the sin of each element of an array', function () { - approx.deepEqual(sin([1, 2, 3]), sin123, EPSILON) + it('should not operate on an array', function () { + assert.throws(() => sin([1, 2, 3]), TypeError) + approx.deepEqual(math.map([1, 2, 3], sin), sin123, EPSILON) }) - it('should return the sin of each element of a matrix', function () { - approx.deepEqual(sin(matrix([1, 2, 3])), matrix(sin123), EPSILON) + it('should not operate on a matrix', function () { + assert.throws(() => sin(matrix([1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([1, 2, 3]), sin), matrix(sin123), EPSILON) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/sinh.test.js b/test/unit-tests/function/trigonometry/sinh.test.js index b0871b721f..8ca1422123 100644 --- a/test/unit-tests/function/trigonometry/sinh.test.js +++ b/test/unit-tests/function/trigonometry/sinh.test.js @@ -76,14 +76,8 @@ describe('sinh', function () { approx.deepEqual(sinh(complex('2 + i')), complex(1.95960104142160589707, 3.16577851321616814674), EPSILON) }) - it('should return the sinh of an angle', function () { - approx.equal(sinh(unit('90deg')), 2.3012989023073, EPSILON) - approx.equal(sinh(unit('-45deg')), -0.86867096148601, EPSILON) - - assert(math.isBigNumber(sinh(unit(math.bignumber(90), 'deg')))) - approx.equal(sinh(unit(math.bignumber(90), 'deg')).toNumber(), 2.3012989023073, EPSILON) - - approx.deepEqual(sinh(unit(complex('2 + i'), 'rad')), complex(1.95960104142160589707, 3.16577851321616814674), EPSILON) + it('should throw an error on an angle', function () { + assert.throws(() => sinh(unit('90deg')), TypeError) }) it('should throw an error if called with an invalid unit', function () { @@ -96,12 +90,14 @@ describe('sinh', function () { const sinh123 = [1.1752011936438014, 3.626860407847, 10.01787492741] - it('should return the sinh of each element of an array', function () { - approx.deepEqual(sinh([1, 2, 3]), sinh123, EPSILON) + it('should not operate on an array', function () { + assert.throws(() => sinh([1, 2, 3]), TypeError) + approx.deepEqual(math.map([1, 2, 3], sinh), sinh123, EPSILON) }) - it('should return the sinh of each element of a matrix', function () { - approx.deepEqual(sinh(matrix([1, 2, 3])), matrix(sinh123), EPSILON) + it('should not operate on a matrix', function () { + assert.throws(() => sinh(matrix([1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([1, 2, 3]), sinh), matrix(sinh123), EPSILON) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/tan.test.js b/test/unit-tests/function/trigonometry/tan.test.js index 2b675d9446..e83bc40564 100644 --- a/test/unit-tests/function/trigonometry/tan.test.js +++ b/test/unit-tests/function/trigonometry/tan.test.js @@ -73,12 +73,14 @@ describe('tan', function () { const tan123 = [1.557407724654902, -2.185039863261519, -0.142546543074278] - it('should return the tan of each element of an array', function () { - approx.deepEqual(tan([1, 2, 3]), tan123) + it('should not operate on an array', function () { + assert.throws(() => tan([1, 2, 3]), TypeError) + approx.deepEqual(math.map([1, 2, 3], tan), tan123) }) - it('should return the tan of each element of a matrix', function () { - approx.deepEqual(tan(matrix([1, 2, 3])), matrix(tan123)) + it('should not operate on a matrix', function () { + assert.throws(() => tan(matrix([1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([1, 2, 3]), tan), matrix(tan123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/trigonometry/tanh.test.js b/test/unit-tests/function/trigonometry/tanh.test.js index 0f42f3cd7b..6b8b4ec42a 100644 --- a/test/unit-tests/function/trigonometry/tanh.test.js +++ b/test/unit-tests/function/trigonometry/tanh.test.js @@ -52,14 +52,8 @@ describe('tanh', function () { approx.deepEqual(tanh(complex('2 + i')), complex(1.0147936161466, 0.033812826079897)) }) - it('should return the tanh of an angle', function () { - approx.equal(tanh(unit('90deg')), 0.91715233566727) - approx.equal(tanh(unit('-45deg')), -0.65579420263267) - - assert(math.isBigNumber(tanh(unit(math.bignumber(90), 'deg')))) - approx.equal(tanh(unit(math.bignumber(90), 'deg')).toNumber(), 0.91715233566727) - - approx.deepEqual(tanh(unit(complex('2 + i'), 'rad')), complex(1.0147936161466, 0.033812826079897)) + it('should throw an error on an angle', function () { + assert.throws(() => tanh(unit('90deg')), TypeError) }) it('should throw an error if called with an invalid unit', function () { @@ -72,12 +66,14 @@ describe('tanh', function () { const tanh123 = [0.76159415595576, 0.96402758007582, 0.99505475368673] - it('should return the tanh of each element of an array', function () { - approx.deepEqual(tanh([1, 2, 3]), tanh123) + it('should not operate on an array', function () { + assert.throws(() => tanh([1, 2, 3]), TypeError) + approx.deepEqual(math.map([1, 2, 3], tanh), tanh123) }) - it('should return the tanh of each element of a matrix', function () { - approx.deepEqual(tanh(matrix([1, 2, 3])), matrix(tanh123)) + it('should not operate on a matrix', function () { + assert.throws(() => tanh(matrix([1, 2, 3])), TypeError) + approx.deepEqual(math.map(matrix([1, 2, 3]), tanh), matrix(tanh123)) }) it('should throw an error in case of invalid number of arguments', function () { diff --git a/test/unit-tests/function/unit/to.test.js b/test/unit-tests/function/unit/to.test.js index 1da0cc4655..184950438c 100644 --- a/test/unit-tests/function/unit/to.test.js +++ b/test/unit-tests/function/unit/to.test.js @@ -70,7 +70,7 @@ describe('to', function () { it('should throw an error if called with a number', function () { assert.throws(function () { math.to(5, unit('m')) }, TypeError) - assert.throws(function () { math.to(unit('5cm'), 2) }, /SyntaxError: "2" contains no units/) + assert.throws(function () { math.to(unit('5cm'), 2) }, TypeError) }) it('should throw an error if called with a string', function () { diff --git a/test/unit-tests/function/utils/hasNumericValue.test.js b/test/unit-tests/function/utils/hasNumericValue.test.js index 5fe5d0bee9..f9ebdf3ffc 100644 --- a/test/unit-tests/function/utils/hasNumericValue.test.js +++ b/test/unit-tests/function/utils/hasNumericValue.test.js @@ -15,6 +15,8 @@ describe('hasNumericValue', function () { assert.strictEqual(hasNumericValue('2'), true) assert.strictEqual(hasNumericValue(' 2'), true) assert.strictEqual(hasNumericValue('2.3'), true) + assert.strictEqual(hasNumericValue(true), true) + assert.strictEqual(hasNumericValue(false), true) assert.strictEqual(hasNumericValue('100a'), false) assert.strictEqual(hasNumericValue('0x11'), true) // The following two tests are not working on IE11 diff --git a/test/unit-tests/function/utils/typeof.test.js b/test/unit-tests/function/utils/typeof.test.js index 5df6623b93..eee698e1c9 100644 --- a/test/unit-tests/function/utils/typeof.test.js +++ b/test/unit-tests/function/utils/typeof.test.js @@ -47,8 +47,8 @@ describe('typeOf', function () { }) it('should return matrix type for a matrix', function () { - assert.strictEqual(math.typeOf(math.matrix()), 'Matrix') - assert.strictEqual(math.typeOf(math.matrix()), 'Matrix') + assert.strictEqual(math.typeOf(math.matrix()), 'DenseMatrix') + assert.strictEqual(math.typeOf(math.matrix([], 'sparse')), 'SparseMatrix') }) it('should return unit type for a unit', function () { @@ -80,7 +80,7 @@ describe('typeOf', function () { it('should return function type for a function', function () { function f1 () {} - assert.strictEqual(math.typeOf(f1), 'Function') + assert.strictEqual(math.typeOf(f1), 'function') }) it('should return function type for a chain', function () { @@ -107,8 +107,10 @@ describe('typeOf', function () { assert.strictEqual(math.typeOf(new math.FunctionNode('f', [])), 'FunctionNode') assert.strictEqual(math.typeOf(indexNode), 'IndexNode') assert.strictEqual(math.typeOf(new math.ObjectNode({})), 'ObjectNode') + assert.strictEqual(math.typeOf(math.parse('a+b')), 'OperatorNode') assert.strictEqual(math.typeOf(new math.ParenthesisNode(constantNode)), 'ParenthesisNode') assert.strictEqual(math.typeOf(new math.RangeNode(constantNode, constantNode)), 'RangeNode') + assert.strictEqual(math.typeOf(math.parse('a new Chain(3).median(4, 5).done(), + /Error:.*median.*rest/) + assert.throws( + () => new Chain(3).ones(2, 'dense').done(), + /Error:.*ones.*rest/) + }) + it('should have a property isChain', function () { const a = new math.Chain(5) assert.strictEqual(a.isChain, true) diff --git a/test/unit-tests/type/matrix/function/matrix.test.js b/test/unit-tests/type/matrix/function/matrix.test.js index f84e50e720..285e908588 100644 --- a/test/unit-tests/type/matrix/function/matrix.test.js +++ b/test/unit-tests/type/matrix/function/matrix.test.js @@ -85,11 +85,11 @@ describe('matrix', function () { }) it('should throw an error if called with too many arguments', function () { - assert.throws(function () { matrix([], 3, 3, 7) }, /TypeError: Too many arguments/) + assert.throws(function () { matrix([], 'dense', 'number', 7) }, /TypeError: Too many arguments/) }) it('should throw an error when called with an invalid storage format', function () { - assert.throws(function () { math.matrix([], 1) }, /TypeError: Unknown matrix type "1"/) + assert.throws(function () { math.matrix([], '1') }, /TypeError: Unknown matrix type "1"/) }) it('should throw an error when called with an unknown storage format', function () { diff --git a/test/unit-tests/type/matrix/function/sparse.test.js b/test/unit-tests/type/matrix/function/sparse.test.js index 8c18b64570..88648cf3c6 100644 --- a/test/unit-tests/type/matrix/function/sparse.test.js +++ b/test/unit-tests/type/matrix/function/sparse.test.js @@ -40,7 +40,7 @@ describe('sparse', function () { }) it('should throw an error if called with too many arguments', function () { - assert.throws(function () { sparse([], 3, 3) }, /TypeError: Too many arguments/) + assert.throws(function () { sparse([], 'number', 3) }, /TypeError: Too many arguments/) }) it('should LaTeX matrix', function () { diff --git a/test/unit-tests/type/numeric.test.js b/test/unit-tests/type/numeric.test.js index 6d57a46855..6994d08b09 100644 --- a/test/unit-tests/type/numeric.test.js +++ b/test/unit-tests/type/numeric.test.js @@ -5,7 +5,11 @@ const numeric = math.numeric describe('numeric', function () { it('should throw if called with wrong number of arguments', function () { assert.throws(() => { numeric() }, /Cannot convert/) - assert.throws(() => { numeric(3.14) }, /Cannot convert/) + assert.throws(() => { numeric(3.14, 'Fraction', 'pi') }, SyntaxError) + }) + + it('should default to converting to number', function () { + assert.strictEqual(numeric('3.14'), 3.14) }) it('should throw if called with invalid argument', function () { diff --git a/test/unit-tests/type/unit/Unit.test.js b/test/unit-tests/type/unit/Unit.test.js index aa6776aaab..990621ed1a 100644 --- a/test/unit-tests/type/unit/Unit.test.js +++ b/test/unit-tests/type/unit/Unit.test.js @@ -20,10 +20,12 @@ describe('Unit', function () { unit1 = new Unit(null, 'kg') assert.strictEqual(unit1.value, null) assert.strictEqual(unit1.units[0].unit.name, 'g') + assert.strictEqual(unit1.valueType(), 'null') unit1 = new Unit(10, 'Hz') assert.strictEqual(unit1.value, 10) assert.strictEqual(unit1.units[0].unit.name, 'Hz') + assert.strictEqual(unit1.valueType(), 'number') unit1 = new Unit(9.81, 'kg m/s^2') assert.strictEqual(unit1.value, 9.81) @@ -36,18 +38,21 @@ describe('Unit', function () { const unit1 = new Unit(math.fraction(1000, 3), 'cm') assert.deepStrictEqual(unit1.value, math.fraction(10, 3)) assert.strictEqual(unit1.units[0].unit.name, 'm') + assert.strictEqual(unit1.valueType(), 'Fraction') }) it('should create a unit with BigNumber value', function () { const unit1 = new Unit(math.bignumber(5000), 'cm') assert.deepStrictEqual(unit1.value, math.bignumber(50)) assert.strictEqual(unit1.units[0].unit.name, 'm') + assert.strictEqual(unit1.valueType(), 'BigNumber') }) it('should create a unit with Complex value', function () { const unit1 = new Unit(math.complex(500, 600), 'cm') assert.deepStrictEqual(unit1.value, math.complex(5, 6)) assert.strictEqual(unit1.units[0].unit.name, 'm') + assert.strictEqual(unit1.valueType(), 'Complex') }) it('should create square meter correctly', function () { diff --git a/types/index.d.ts b/types/index.d.ts index 5e43e2e11d..9e174824d3 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -647,7 +647,7 @@ declare namespace math { * @returns The created Matrix */ matrix( - data: MathCollection, + data: MathCollection | string[], format?: 'sparse' | 'dense', dataType?: string ): Matrix @@ -890,15 +890,23 @@ declare namespace math { */ simplify: Simplify + simplifyConstant(expr: MathNode | string, options?: SimplifyOptions) + simplifyCore(expr: MathNode | string, options?: SimplifyOptions) + /** * Replaces variable nodes with their scoped values * @param node Tree to replace variable nodes in * @param scope Scope to read/write variables */ // eslint-disable-next-line @typescript-eslint/no-explicit-any - resolve(node: MathNode, scope?: Record): MathNode + resolve(node: MathNode | string, scope?: Record): MathNode + resolve( + node: (MathNode | string)[], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + scope?: Record + ): MathNode[] // eslint-disable-next-line @typescript-eslint/no-explicit-any - resolve(node: MathNode[], scope?: Record): MathNode[] + resolve(node: Matrix, scope?: Record): Matrix /** * Calculate the Sparse Matrix LU decomposition with full pivoting. @@ -960,8 +968,7 @@ declare namespace math { add(x: MathType, y: MathType): MathType /** - * Calculate the cubic root of a value. For matrices, the function is - * evaluated element wise. + * Calculate the cubic root of a value. * @param x Value for which to calculate the cubic root. * @param allRoots Optional, false by default. Only applicable when x is * a number or complex number. If true, all complex roots are returned, @@ -969,10 +976,9 @@ declare namespace math { * @returns Returns the cubic root of x */ cbrt(x: number, allRoots?: boolean): number - cbrt(x: BigNumber, allRoots?: boolean): BigNumber + cbrt(x: BigNumber): BigNumber cbrt(x: Complex, allRoots?: boolean): Complex - cbrt(x: MathArray, allRoots?: boolean): MathArray - cbrt(x: Matrix, allRoots?: boolean): Matrix + cbrt(x: Unit): Unit // Rounding functions, grouped for similarity, even though it breaks // the alphabetic order among arithmetic functions. @@ -1042,8 +1048,6 @@ declare namespace math { cube(x: BigNumber): BigNumber cube(x: Fraction): Fraction cube(x: Complex): Complex - cube(x: MathArray): MathArray - cube(x: Matrix): Matrix cube(x: Unit): Unit /** @@ -1093,8 +1097,6 @@ declare namespace math { exp(x: number): number exp(x: BigNumber): BigNumber exp(x: Complex): Complex - exp(x: MathArray): MathArray - exp(x: Matrix): Matrix /** * Calculate the value of subtracting 1 from the exponential value. For @@ -1105,8 +1107,6 @@ declare namespace math { expm1(x: number): number expm1(x: BigNumber): BigNumber expm1(x: Complex): Complex - expm1(x: MathArray): MathArray - expm1(x: Matrix): Matrix /** * Calculate the greatest common divisor for two or more values or @@ -1147,14 +1147,13 @@ declare namespace math { lcm(a: Matrix, b: Matrix): Matrix /** - * Calculate the logarithm of a value. For matrices, the function is - * evaluated element wise. + * Calculate the logarithm of a value. * @param x Value for which to calculate the logarithm. * @param base Optional base for the logarithm. If not provided, the * natural logarithm of x is calculated. Default value: e. * @returns Returns the logarithm of x */ - log( + log( x: T, base?: number | BigNumber | Complex ): NoLiteralType @@ -1273,21 +1272,19 @@ declare namespace math { sign(x: Unit): Unit /** - * Calculate the square root of a value. For matrices, the function is - * evaluated element wise. + * Calculate the square root of a value. For matrices, use either + * sqrtm for the matrix square root, or map(M, sqrt) to take the + * square root element wise. * @param x Value for which to calculate the square root * @returns Returns the square root of x */ - sqrt(x: number): number + sqrt(x: number): number | Complex sqrt(x: BigNumber): BigNumber sqrt(x: Complex): Complex - sqrt(x: MathArray): MathArray - sqrt(x: Matrix): Matrix sqrt(x: Unit): Unit /** - * Compute the square of a value, x * x. For matrices, the function is - * evaluated element wise. + * Compute the square of a value, x * x. * @param x Number for which to calculate the square * @returns Squared value */ @@ -1295,8 +1292,6 @@ declare namespace math { square(x: BigNumber): BigNumber square(x: Fraction): Fraction square(x: Complex): Complex - square(x: MathArray): MathArray - square(x: Matrix): Matrix square(x: Unit): Unit /** @@ -1850,14 +1845,37 @@ declare namespace math { * @param format The matrix storage format * @returns A matrix filled with ones */ - ones(size: number | number[], format?: string): MathCollection + ones( + size?: number | number[] | BigNumber | BigNumber[], + format?: string + ): MathCollection + /** + * @param m The x dimension of the matrix + * @param n The y dimension of the matrix + * @param format The matrix storage format + * @returns A matrix filled with ones + */ + ones( + m: number | BigNumber, + n: number | BigNumber, + format?: string + ): MathCollection /** * @param m The x dimension of the matrix - * @param n The y dimension of the amtrix + * @param n The y dimension of the matrix + * @param p The z dimension of the matrix * @param format The matrix storage format * @returns A matrix filled with ones */ - ones(m: number, n: number, format?: string): MathCollection + ones( + m: number | BigNumber, + n: number | BigNumber, + p: number | BigNumber, + format?: string + ): MathCollection + /** Actually ones can take an arbitrary number of dimensions before the + ** optional format, not sure how to write that in TypeScript + **/ /** * Partition-based selection of an array or 1D matrix. Will find the kth @@ -2038,14 +2056,37 @@ declare namespace math { * @param format The matrix storage format * @returns A matrix filled with zeros */ - zeros(size: number | number[], format?: string): MathCollection + zeros( + size?: number | number[] | BigNumber | BigNumber[], + format?: string + ): MathCollection + /** + * @param m The x dimension of the matrix + * @param n The y dimension of the matrix + * @param format The matrix storage format + * @returns A matrix filled with zeros + */ + zeros( + m: number | BigNumber, + n: number | BigNumber, + format?: string + ): MathCollection /** * @param m The x dimension of the matrix * @param n The y dimension of the matrix + * @param p The z dimension of the matrix * @param format The matrix storage format * @returns A matrix filled with zeros */ - zeros(m: number, n: number, format?: string): MathCollection + zeros( + m: number | BigNumber, + n: number | BigNumber, + p: number | BigNumber, + format?: string + ): MathCollection + /** Actually zeros can take any number of dimensions before the + ** optional format, not sure how to write that in TypeScript + **/ /** * Calculate N-dimensional fourier transform @@ -2092,13 +2133,11 @@ declare namespace math { /** * Compute the gamma function of a value using Lanczos approximation for * small values, and an extended Stirling approximation for large - * values. For matrices, the function is evaluated element wise. + * values. * @param n A real or complex number * @returns The gamma of n */ - gamma( - n: T - ): NoLiteralType + gamma(n: T): NoLiteralType /** * Calculate the Kullback-Leibler (KL) divergence between two @@ -2654,6 +2693,13 @@ declare namespace math { // eslint-disable-next-line @typescript-eslint/no-explicit-any sum(array: MathCollection): any + /** + * Count the number of elements of a matrix, array or string. + * @param x A matrix, array or string. + * @returns The number of members passed in parameters + */ + count(x: MathCollection | string): number + /** * Compute the cumulative sum of a matrix or a list with values. * In case of a (multi dimensional) array or matrix, the cumulative sums @@ -2770,133 +2816,108 @@ declare namespace math { ************************************************************************/ /** - * Calculate the inverse cosine of a value. For matrices, the function - * is evaluated element wise. + * Calculate the inverse cosine of a value. * @param x Function input * @returns The arc cosine of x */ - acos(x: number): number + acos(x: number): number | Complex acos(x: BigNumber): BigNumber acos(x: Complex): Complex - acos(x: MathArray): MathArray - acos(x: Matrix): Matrix /** * Calculate the hyperbolic arccos of a value, defined as acosh(x) = - * ln(sqrt(x^2 - 1) + x). For matrices, the function is evaluated - * element wise. + * ln(sqrt(x^2 - 1) + x). * @param x Function input * @returns The hyperbolic arccosine of x */ - acosh(x: number): number + acosh(x: number): number | Complex acosh(x: BigNumber): BigNumber acosh(x: Complex): Complex - acosh(x: MathArray): MathArray - acosh(x: Matrix): Matrix /** - * Calculate the inverse cotangent of a value. For matrices, the - * function is evaluated element wise. + * Calculate the inverse cotangent of a value. * @param x Function input * @returns The arc cotangent of x */ acot(x: number): number acot(x: BigNumber): BigNumber - acot(x: MathArray): MathArray - acot(x: Matrix): Matrix + acot(x: Complex): Complex /** * Calculate the hyperbolic arccotangent of a value, defined as acoth(x) - * = (ln((x+1)/x) + ln(x/(x-1))) / 2. For matrices, the function is - * evaluated element wise. + * = (ln((x+1)/x) + ln(x/(x-1))) / 2. * @param x Function input * @returns The hyperbolic arccotangent of x */ acoth(x: number): number acoth(x: BigNumber): BigNumber - acoth(x: MathArray): MathArray - acoth(x: Matrix): Matrix + acoth(x: Complex): Complex /** - * Calculate the inverse cosecant of a value. For matrices, the function - * is evaluated element wise. + * Calculate the inverse cosecant of a value. * @param x Function input * @returns The arc cosecant of x */ - acsc(x: number): number + acsc(x: number): number | Complex acsc(x: BigNumber): BigNumber - acsc(x: MathArray): MathArray - acsc(x: Matrix): Matrix + acsc(x: Complex): Complex /** * Calculate the hyperbolic arccosecant of a value, defined as acsch(x) - * = ln(1/x + sqrt(1/x^2 + 1)). For matrices, the function is evaluated - * element wise. + * = ln(1/x + sqrt(1/x^2 + 1)). * @param x Function input * @returns The hyperbolic arccosecant of x */ acsch(x: number): number acsch(x: BigNumber): BigNumber - acsch(x: MathArray): MathArray - acsch(x: Matrix): Matrix + acsch(x: Complex): Complex /** - * Calculate the inverse secant of a value. For matrices, the function - * is evaluated element wise. + * Calculate the inverse secant of a value. * @param x Function input * @returns The arc secant of x */ - asec(x: number): number + asec(x: number): number | Complex asec(x: BigNumber): BigNumber - asec(x: MathArray): MathArray - asec(x: Matrix): Matrix + asec(x: Complex): Complex /** * Calculate the hyperbolic arcsecant of a value, defined as asech(x) = - * ln(sqrt(1/x^2 - 1) + 1/x). For matrices, the function is evaluated - * element wise. + * ln(sqrt(1/x^2 - 1) + 1/x). * @param x Function input * @returns The hyperbolic arcsecant of x */ - asech(x: number): number + asech(x: number): number | Complex asech(x: BigNumber): BigNumber - asech(x: MathArray): MathArray - asech(x: Matrix): Matrix + asech(x: Complex): Complex /** - * Calculate the inverse sine of a value. For matrices, the function is - * evaluated element wise. + * Calculate the inverse sine of a value. * @param x Function input * @returns The arc sine of x */ - asin(x: number): number + asin(x: number): number | Complex asin(x: BigNumber): BigNumber asin(x: Complex): Complex - asin(x: MathArray): MathArray - asin(x: Matrix): Matrix /** * Calculate the hyperbolic arcsine of a value, defined as asinh(x) = - * ln(x + sqrt(x^2 + 1)). For matrices, the function is evaluated - * element wise. + * ln(x + sqrt(x^2 + 1)). * @param x Function input * @returns The hyperbolic arcsine of x */ asinh(x: number): number asinh(x: BigNumber): BigNumber - asinh(x: MathArray): MathArray - asinh(x: Matrix): Matrix + asinh(x: Complex): Complex /** - * Calculate the inverse tangent of a value. For matrices, the function - * is evaluated element wise. + * Calculate the inverse tangent of a value. * @param x Function input * @returns The arc tangent of x */ atan(x: number): number atan(x: BigNumber): BigNumber - atan(x: MathArray): MathArray - atan(x: Matrix): Matrix + atan(x: Complex): Complex /** * Calculate the inverse tangent function with two arguments, y/x. By @@ -2910,156 +2931,127 @@ declare namespace math { /** * Calculate the hyperbolic arctangent of a value, defined as atanh(x) = - * ln((1 + x)/(1 - x)) / 2. For matrices, the function is evaluated - * element wise. + * ln((1 + x)/(1 - x)) / 2. * @param x Function input * @returns The hyperbolic arctangent of x */ - atanh(x: number): number + atanh(x: number): number | Complex atanh(x: BigNumber): BigNumber - atanh(x: MathArray): MathArray - atanh(x: Matrix): Matrix + atanh(x: Complex): Complex /** - * Calculate the cosine of a value. For matrices, the function is - * evaluated element wise. + * Calculate the cosine of a value. * @param x Function input * @returns The cosine of x */ cos(x: number | Unit): number cos(x: BigNumber): BigNumber cos(x: Complex): Complex - cos(x: MathArray): MathArray - cos(x: Matrix): Matrix /** * Calculate the hyperbolic cosine of a value, defined as cosh(x) = 1/2 - * * (exp(x) + exp(-x)). For matrices, the function is evaluated element - * wise. + * * (exp(x) + exp(-x)). * @param x Function input * @returns The hyperbolic cosine of x */ cosh(x: number | Unit): number cosh(x: BigNumber): BigNumber cosh(x: Complex): Complex - cosh(x: MathArray): MathArray - cosh(x: Matrix): Matrix /** * Calculate the cotangent of a value. cot(x) is defined as 1 / tan(x). - * For matrices, the function is evaluated element wise. * @param x Function input * @returns The cotangent of x */ cot(x: number | Unit): number + cot(x: BigNumber): BigNumber cot(x: Complex): Complex - cot(x: MathArray): MathArray - cot(x: Matrix): Matrix /** * Calculate the hyperbolic cotangent of a value, defined as coth(x) = 1 - * / tanh(x). For matrices, the function is evaluated element wise. + * / tanh(x). * @param x Function input * @returns The hyperbolic cotangent of x */ coth(x: number | Unit): number + coth(x: BigNumber): BigNumber coth(x: Complex): Complex - coth(x: MathArray): MathArray - coth(x: Matrix): Matrix /** - * Calculate the cosecant of a value, defined as csc(x) = 1/sin(x). For - * matrices, the function is evaluated element wise. + * Calculate the cosecant of a value, defined as csc(x) = 1/sin(x). * @param x Function input * @returns The cosecant hof x */ csc(x: number | Unit): number + csc(x: BigNumber): BigNumber csc(x: Complex): Complex - csc(x: MathArray): MathArray - csc(x: Matrix): Matrix /** * Calculate the hyperbolic cosecant of a value, defined as csch(x) = 1 - * / sinh(x). For matrices, the function is evaluated element wise. + * / sinh(x). * @param x Function input * @returns The hyperbolic cosecant of x */ csch(x: number | Unit): number + csch(x: BigNumber): BigNumber csch(x: Complex): Complex - csch(x: MathArray): MathArray - csch(x: Matrix): Matrix /** - * Calculate the secant of a value, defined as sec(x) = 1/cos(x). For - * matrices, the function is evaluated element wise. + * Calculate the secant of a value, defined as sec(x) = 1/cos(x). * @param x Function input * @returns The secant of x */ sec(x: number | Unit): number + sec(x: BigNumber): BigNumber sec(x: Complex): Complex - sec(x: MathArray): MathArray - sec(x: Matrix): Matrix /** * Calculate the hyperbolic secant of a value, defined as sech(x) = 1 / - * cosh(x). For matrices, the function is evaluated element wise. + * cosh(x). * @param x Function input * @returns The hyperbolic secant of x */ sech(x: number | Unit): number + sech(x: BigNumber): BigNumber sech(x: Complex): Complex - sech(x: MathArray): MathArray - sech(x: Matrix): Matrix /** - * Calculate the sine of a value. For matrices, the function is - * evaluated element wise. + * Calculate the sine of a value. * @param x Function input * @returns The sine of x */ sin(x: number | Unit): number sin(x: BigNumber): BigNumber sin(x: Complex): Complex - sin(x: MathArray): MathArray - sin(x: Matrix): Matrix /** * Calculate the hyperbolic sine of a value, defined as sinh(x) = 1/2 * - * (exp(x) - exp(-x)). For matrices, the function is evaluated element - * wise. + * (exp(x) - exp(-x)). * @param x Function input * @returns The hyperbolic sine of x */ sinh(x: number | Unit): number sinh(x: BigNumber): BigNumber sinh(x: Complex): Complex - sinh(x: MathArray): MathArray - sinh(x: Matrix): Matrix /** * Calculate the tangent of a value. tan(x) is equal to sin(x) / cos(x). - * For matrices, the function is evaluated element wise. * @param x Function input * @returns The tangent of x */ tan(x: number | Unit): number tan(x: BigNumber): BigNumber tan(x: Complex): Complex - tan(x: MathArray): MathArray - tan(x: Matrix): Matrix /** * Calculate the hyperbolic tangent of a value, defined as tanh(x) = - * (exp(2 * x) - 1) / (exp(2 * x) + 1). For matrices, the function is - * evaluated element wise. + * (exp(2 * x) - 1) / (exp(2 * x) + 1). * @param x Function input * @returns The hyperbolic tangent of x */ tanh(x: number | Unit): number tanh(x: BigNumber): BigNumber tanh(x: Complex): Complex - tanh(x: MathArray): MathArray - tanh(x: Matrix): Matrix /************************************************************************* * Unit functions @@ -4415,11 +4407,18 @@ declare namespace math { simplify( this: MathJsChain, rules?: SimplifyRule[], - scope?: object + scope?: Map | object, + options?: SimplifyOptions ): MathJsChain - // TODO check that this should even be here... - simplifyCore(expr: MathNode): MathNode + simplifyConstant( + this: MathJsChain, + options?: SimplifyOptions + ): MathJsChain + simplifyCore( + this: MathJsChain, + options?: SimplifyOptions + ): MathJsChain /** * Calculate the Sparse Matrix LU decomposition with full pivoting. @@ -4503,16 +4502,9 @@ declare namespace math { * if false (default) the principal root is returned. */ cbrt(this: MathJsChain, allRoots?: boolean): MathJsChain - cbrt( - this: MathJsChain, - allRoots?: boolean - ): MathJsChain + cbrt(this: MathJsChain): MathJsChain cbrt(this: MathJsChain, allRoots?: boolean): MathJsChain - cbrt( - this: MathJsChain, - allRoots?: boolean - ): MathJsChain - cbrt(this: MathJsChain, allRoots?: boolean): MathJsChain + cbrt(this: MathJsChain, allRoots?: boolean): MathJsChain // Rounding functions grouped for similarity @@ -4583,8 +4575,6 @@ declare namespace math { cube(this: MathJsChain): MathJsChain cube(this: MathJsChain): MathJsChain cube(this: MathJsChain): MathJsChain - cube(this: MathJsChain): MathJsChain - cube(this: MathJsChain): MathJsChain cube(this: MathJsChain): MathJsChain /** @@ -4624,8 +4614,6 @@ declare namespace math { exp(this: MathJsChain): MathJsChain exp(this: MathJsChain): MathJsChain exp(this: MathJsChain): MathJsChain - exp(this: MathJsChain): MathJsChain - exp(this: MathJsChain): MathJsChain /** * Calculate the value of subtracting 1 from the exponential value. For @@ -4634,8 +4622,6 @@ declare namespace math { expm1(this: MathJsChain): MathJsChain expm1(this: MathJsChain): MathJsChain expm1(this: MathJsChain): MathJsChain - expm1(this: MathJsChain): MathJsChain - expm1(this: MathJsChain): MathJsChain /** * Calculate the greatest common divisor for two or more values or @@ -4877,6 +4863,21 @@ declare namespace math { b: number | BigNumber ): MathJsChain + /** + * Count the number of elements of a matrix, array or string. + */ + count(this: MathJsChain): MathJsChain + count(this: MathJsChain): MathJsChain + + /** + * Compute the sum of a matrix or a list with values. In case of a + * (multi dimensional) array or matrix, the sum of all elements will be + * calculated. + */ + sum( + this: MathJsChain> + ): MathJsChain + sum(this: MathJsChain): MathJsChain /************************************************************************* * Bitwise functions ************************************************************************/ @@ -5290,16 +5291,7 @@ declare namespace math { * @param format The matrix storage format */ ones( - this: MathJsChain, - format?: string - ): MathJsChain - - /** - * @param format The matrix storage format - */ - ones( - this: MathJsChain, - n: number, + this: MathJsChain, format?: string ): MathJsChain @@ -5438,17 +5430,7 @@ declare namespace math { * @returns A matrix filled with zeros */ zeros( - this: MathJsChain, - format?: string - ): MathJsChain - - /** - * @param n The y dimension of the matrix - * @param format The matrix storage format - */ - zeros( - this: MathJsChain, - n: number, + this: MathJsChain, format?: string ): MathJsChain diff --git a/types/index.ts b/types/index.ts index 9fcc814329..eeac6295de 100644 --- a/types/index.ts +++ b/types/index.ts @@ -89,6 +89,22 @@ Basic usage examples math.variance(math.variance(m2by3, 'uncorrected')) + // count and sum check + math.count([10, 10, 10]) + math.count([ + [1, 2, 3], + [4, 5, 6], + ]) + math.count('mathjs') + + math.sum(1, 2, 3, 4) + math.sum([1, 2, 3, 4]) + math.sum([ + [1, 2], + [3, 4], + [5, 6], + ]) + // expressions math.evaluate('1.2 * (2 + 4.5)') @@ -135,6 +151,23 @@ Bignumbers examples } } +/* + Arithmetic function examples +*/ +{ + const math = create(all, {}) + + const _e: number = math.exp(1) + const _bige: BigNumber = math.exp(math.bignumber(1)) + const _compe: Complex = math.exp(math.complex(1, 0)) + // TODO: Typescript type declarations are not understanding typed-function's + // automatic conversions, so the following do not work: + + // const _compeagain: Complex = math.exp(math.fraction(1, 1)) + // const _eagain: number = math.exp('1') + // const _eanother: number = math.exp(true) +} + /* Chaining examples */ @@ -525,21 +558,23 @@ Chaining examples expectTypeOf(math.chain(math.complex(1, 2)).cbrt()).toMatchTypeOf< MathJsChain >() - expectTypeOf(math.chain([1, 2]).cbrt()).toMatchTypeOf< - MathJsChain - >() - expectTypeOf( - math - .chain( - math.matrix([ - [1, 2], - [3, 4], - ]) - ) - .cbrt() - ).toMatchTypeOf>() + // @ts-expect-error ... verify cbrt does not run on arrays. + assert.throws(() => math.chain([1, 2]).cbrt(), TypeError) + assert.throws( + () => + // @ts-expect-error ... verify cbrt does not run on matrices. + math + .chain( + math.matrix([ + [1, 2], + [3, 4], + ]) + ) + .cbrt(), + TypeError + ) - // cbrt + // ceil expectTypeOf(math.chain(1).ceil()).toMatchTypeOf< MathJsChain >() @@ -582,19 +617,23 @@ Chaining examples expectTypeOf(math.chain(math.complex(1, 2)).cube()).toMatchTypeOf< MathJsChain >() - expectTypeOf(math.chain([1, 2]).cube()).toMatchTypeOf< - MathJsChain - >() - expectTypeOf( - math - .chain( - math.matrix([ - [1, 2], - [3, 4], - ]) - ) - .cube() - ).toMatchTypeOf>() + + // @ts-expect-error ... verify cube does not run on arrays. + assert.throws(() => math.chain([1, 2]).cube(), TypeError) + assert.throws( + () => + // @ts-expect-error ... verify cube does not run on matrices. + math + .chain( + math.matrix([ + [1, 2], + [3, 4], + ]) + ) + .cube(), + TypeError + ) + expectTypeOf(math.chain(math.unit('furlong')).cube()).toMatchTypeOf< MathJsChain >() @@ -656,33 +695,40 @@ Chaining examples // exp expectTypeOf(math.chain(1).exp()).toMatchTypeOf>() - expectTypeOf(math.chain([1, 2]).exp()).toMatchTypeOf>() - expectTypeOf( - math - .chain( - math.matrix([ - [1, 2], - [3, 4], - ]) - ) - .exp() - ).toMatchTypeOf>() + // @ts-expect-error ... verify exp does not run on arrays. + assert.throws(() => math.chain([1, 2]).exp(), TypeError) + assert.throws( + () => + // @ts-expect-error ... verify exp does not run on matrices. + math + .chain( + math.matrix([ + [1, 2], + [3, 4], + ]) + ) + .exp(), + TypeError + ) // expm1 expectTypeOf(math.chain(1).expm1()).toMatchTypeOf>() - expectTypeOf(math.chain([1, 2]).expm1()).toMatchTypeOf< - MathJsChain - >() - expectTypeOf( - math - .chain( - math.matrix([ - [1, 2], - [3, 4], - ]) - ) - .expm1() - ).toMatchTypeOf>() + + // @ts-expect-error ... verify expm1 does not run on arrays + assert.throws(() => math.chain([1, 2]).expm1(), TypeError) + assert.throws( + () => + // @ts-expect-error ... verify expm1 does not run on arrays + math + .chain( + math.matrix([ + [1, 2], + [3, 4], + ]) + ) + .expm1(), + TypeError + ) // gcd expectTypeOf(math.chain([1, 2]).gcd(3)).toMatchTypeOf>() @@ -766,6 +812,11 @@ Chaining examples .log10() ).toMatchTypeOf>() + expectTypeOf(math.chain([1, 2]).count()).toMatchTypeOf>() + expectTypeOf(math.chain('mathjs').count()).toMatchTypeOf< + MathJsChain + >() + expectTypeOf(math.chain([1, 2]).sum()).toMatchTypeOf>() // TODO complete the rest of these... } @@ -776,6 +827,8 @@ Simplify examples const math = create(all) math.simplify('2 * 1 * x ^ (2 - 1)') + math.simplifyConstant('2 * 1 * x ^ (2 - 1)') + math.simplifyCore('2 * 1 * x ^ (2 - 1)') math.simplify('2 * 3 * x', { x: 4 }) expectTypeOf(math.simplify.rules).toMatchTypeOf> @@ -784,6 +837,7 @@ Simplify examples math.simplify(f) math.simplify('0.4 * x', {}, { exactFractions: true }) + math.simplifyConstant('0.4 * x', { exactFractions: true }) math.simplify('0.4 * x', {}, { exactFractions: false }) math.simplify('0.4 * x', {}, { fractionsLimit: 2 }) math.simplify('0.4 * x', {}, { consoleDebug: false }) @@ -849,6 +903,13 @@ Simplify examples }, (node: MathNode) => node, ]) + math.simplifyCore('0.4 * x + 0', { exactFractions: false }) + + math + .chain('0.4 * x + 0') + .parse() + .simplifyCore({ exactFractions: false }) + .simplifyConstant() } /* @@ -883,8 +944,13 @@ Complex numbers examples // create a complex number from polar coordinates { - const p: math.PolarCoordinates = { r: math.sqrt(2), phi: math.pi / 4 } - const _c: math.Complex = math.complex(p) + const p: math.PolarCoordinates = { + r: math.sqrt(2) as number, // must be real but a sqrt could be Complex + phi: math.pi / 4, + } + const c: math.Complex = math.complex(p) + assert.strictEqual(c.im, 1) + assert.ok(Math.abs(c.re - 1) < 1e-12) } // get polar coordinates of a complex number @@ -1055,6 +1121,11 @@ Matrices examples const b: math.Matrix = math.matrix(math.ones([2, 3])) b.size() + // @ts-expect-error ... ones() in a chain cannot take more dimensions + assert.throws(() => math.chain(3).ones(2, 'dense').done(), /Error:.*ones/) + // @ts-expect-error ... and neither can zeros() + assert.throws(() => math.chain(3).zeros(2, 'sparse').done(), /Error:.*zeros/) + // the Array data of a Matrix can be retrieved using valueOf() const _array = a.valueOf() @@ -1062,7 +1133,7 @@ Matrices examples const _clone: math.Matrix = a.clone() // perform operations with matrices - math.sqrt(a) + math.map(a, math.sqrt) math.factorial(a) // create and manipulate matrices. Arrays and Matrices can be used mixed. @@ -2088,11 +2159,16 @@ Resolve examples { const math = create(all, {}) + expectTypeOf(math.resolve('x + y')).toMatchTypeOf() expectTypeOf(math.resolve(math.parse('x + y'))).toMatchTypeOf() expectTypeOf( math.resolve(math.parse('x + y'), { x: 0 }) ).toMatchTypeOf() - expectTypeOf(math.resolve([math.parse('x + y')], { x: 0 })).toMatchTypeOf< - MathNode[] - >() + expectTypeOf(math.resolve('x + y', { x: 0 })).toMatchTypeOf() + expectTypeOf( + math.resolve([math.parse('x + y'), 'x*x'], { x: 0 }) + ).toMatchTypeOf() + expectTypeOf( + math.resolve(math.matrix(['x', 'y'])) + ).toMatchTypeOf() }