From 59320053fd35e64351713c4ef32af37df1f4c425 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Tue, 19 Jul 2022 12:04:35 +0200 Subject: [PATCH] V11 with typed-function@3 (#2560) * refactor: Remove the automatic conversion from number to string. (#2482) This is a breaking change. However, nothing in the unit tests or examples actually depended on such a conversion, and it's difficult to construct situations in which it's necessary. The best such example is e.g. `count(57)` which formerly gave the number of digits in its numeric argument. Of course, after this commit, that behavior can still be obtained by the just slightly longer expression `count(string(57))` The change is proposed in preparation for an addition of new facilities/ handlers to allow symbolic computation in a couple of different ways (see #2475 and #2470). * feat(simplifyCore): convert equivalent function calls into operators (#2466) * feat(simplifyCore): convert equivalent function calls into operators Resolves #2415. * docs: Every operator has a function form Also documents the new behavior of simplifyCore to convert function calls into any equivalent operator form they may have. Also fixes the syntax errors so that simplifyCore will successfully doctest. * docs: Fix table syntax for operator->function correspondence * fix(parse): Implement amended "Rule 2" As per the discussion in #2370, the amended "Rule 2" is "when having a division followed by an implicit multiplication, the division gets higher precedence over the implicit multiplication when (a) the numerator is a constant with optionally a prefix operator (-, +, ~), and (b) the denominator is a constant." This commit implements that behavior and adds tests for it. Resolves #2370. * fix: OperatorNode.toString() outputs match implicit multiplication parsing Also greatly extends the tests on OperatorNode.toString() and .toTex(), and ensures that all tests are performed on both. (toHTML() is still a testing stepchild.) Also fixes other small bugs in .toString() and .toTex() revealed by the new tests. Resolves #1431. * test(parse): More cases of implicit multiplication * refactor: Alter the precedence of implicit multiplication This greatly simplifies OperatorNode:calculateNecessaryParentheses, as opposed to trying to correct for the change in precedence after the fact. * Fix broken unit test * Replace `options && options.implicit` with `options?.implicit` * Replace `options?.implicit` with `options && options.implicit` again, it breaks the Node 12 tests * chore: Prevent confusion with standard matrix functions. (#2465) * chore: Prevent consfusion with standard matrix functions. Prior to this commit, many functions operated elementwise on matrices even though in standard mathematical usage they have a different meaning on square matrices. Since the elementwise operation is easily recoverable using `math.map`, this commit removes the elementwise operation on arrays and matrices from these functions. Affected functions include all trigonometric functions, exp, log, gamma, square, sqrt, cube, and cbrt. Resolves #2440. * chore(typescript): Revise usages in light of changes sqrt() is now correctly typed as `number | Complex` and so must be explicitly cast to number when called on a positive and used where a Complex is disallowed; sqrt() no longer applies to matrices at all. * feat: Provide better error messages for v10 -> v11 transition Uses new `typed.onMismatch` handler so that matrix calls that used to work will suggest a replacement. * Fix #2412: let function diff return an empty matrix when the input contains only one element (#2422) * Fix #2412: let function diff return an empty matrix when the input has only one element * Undo changes in History in this fixme * Add TypeScript definitions for src/utils/is.js (#2432) This is a first step toward full publication of these functions, that were already being exported by mathjs but had not yet had the associated actions (documentation/available in parser/typed, etc.) Also, makes most of them into TypeScript type guards, and adds Matrix as a constructor type. Resolved #2431. Co-authored-by: Glen Whitney * test: add two-dimensional test cases for diff of length 1 Co-authored-by: Chris Chudzicki Co-authored-by: Glen Whitney * Refactor/simplify core cleanup (#2490) * refactor: don't simplify constants in simplifyCore Keeps the operation of simplifyCore cleanly separate from simplifyConstant. * fix; handle multiple consecutive operations in simplifyCore() Also adds support for logical operators. Resolves #2484. * feat: export simplifyConstant Now that simplifyCore does not do any constant folding, clients may wish to access that behavior via simplifyConstant. Moreover, exporting it makes it easier to use in custom rule lists for simplify(). Also adds docs, embedded docs, and tests for simplifyConstant(). Also fixes simplifyCore() on logical functions (they always return boolean, rather than "short-circuiting"). Resolves #2459. * refactor: Rename matrix algorithms to stay sane in next refactor * refactor: Create a generator for boilerplate matrix versions of operations This reduces code length and duplication, and significantly reduces the number of instances of 'this' that will require replacement when moving on top of typed-function v3. * refactor: add automatic conversion from string to Node Eliminates many `this` calls in src/function/algebra, which will help conversion to typed-function v3a. Also make `resolve` into a typed function so that it will now work on strings as well, and adds a test that it does. * refactor: Use temporary conversions to simplify typed-function definitions Specifically, temporarily converting Object to Map eases the definition of 'simplify' and a new, generally ignored type 'identifier' (a subtype of 'string') with a temporary conversion to 'SymbolNode' simplifies the definition of 'derivative'. These refactors eliminate multiple instances of this, which will ease conversion to typed-function v3a. * refactor: Speed up utils/is.js typeOf function In preparation for using it as the function selector for the Unit class. Also fixes the inconsistency between the `typed` type hierarchy 'function' and typeOf returning 'Function' in favor of 'function', again to minimize the special cases in typeOf * feat(Unit): Add a method giving the (string name of the) type of the value E.g. `math.unit('5cm').valType()` returns `number`. Also uses this for an internal method that directly gives the number converter for a Unit. Also fixes lint errors from previous commit (not clean, I know, I forgot that build-and-test does not run lint). Adds tests for unit.valType() * refactor: Eliminate hyperbolic functions operating on angles There is no mathematical meaning to a hyperbolic function operating on an angle (the proper units of its argument is actually area), and it eliminates a number of uses of `this`, so remove such arguments. * refactor: Remove miscellaneous unnecessary typed-function this refs * refactor: Adapt to typed-function v3a Mostly this involves replaceing instances of 'this' with used of (preferably) typed.referTo() or typed.referToSelf(). Some repeated batterns of boilerpolate signatures within different divisions of functions (bitwise, relational, trigonometry) were factored out into their own files and reused in several of the individual functions. * tests: Only require that derivative tests mention the proper node type * refactor: remove typed.ignore * chore: Update to typed-function 3.0 Also had to deal with new typing for `resolve()` in that it now accepts strings and Matrices; added tests for the new possibilities for `resolve()`, and eliminated empty comments from the Node representation of parsed strings as they can't really be doing anyone any good and they are a pain for testing. Also updates the TypeScript declarations and tests for `resolve()` * chore: Object.hasOwn not supported in Node 14 Also removes 'resolve' from the known failing doc tests, now that it handles strings. * chore: Drop ES5 / IE 11 support. * fix(types): Remove no-longer-implementd matrix overloads * test(identifier): As requested in review item 2 * refactor(Unit): valType => valueType as per review item 3 * test(hasNumericValue): Test boolean arguments as per review item 4 * refactor(Node): Use class syntax rather than assigning prototypes This change simplifies the typeOf() function, because now all subclasses of Node have the expected constructor name. Also, reformats the documentation of the typeOf() function so that the doc test of that function will serve as an exhaustive test that the bundle returns the proper types. * Prevent chain functions from matching stored value with a rest parameter (#2559) * chore: Prevent confusion with standard matrix functions. (#2465) * chore: Prevent consfusion with standard matrix functions. Prior to this commit, many functions operated elementwise on matrices even though in standard mathematical usage they have a different meaning on square matrices. Since the elementwise operation is easily recoverable using `math.map`, this commit removes the elementwise operation on arrays and matrices from these functions. Affected functions include all trigonometric functions, exp, log, gamma, square, sqrt, cube, and cbrt. Resolves #2440. * chore(typescript): Revise usages in light of changes sqrt() is now correctly typed as `number | Complex` and so must be explicitly cast to number when called on a positive and used where a Complex is disallowed; sqrt() no longer applies to matrices at all. * feat: Provide better error messages for v10 -> v11 transition Uses new `typed.onMismatch` handler so that matrix calls that used to work will suggest a replacement. * fix: prevent chain from matching rest parameter with stored value Since the revised code needs the isTypedFunction predicate, switch to using the typed-function implementation for that throughout mathjs, rather than rolling our own here. Also adds a test that chain() no longer allows this kind of usage. Removes the two type declarations in types/index.d.ts that were allowing this sort of "split rest" call and added tests that such calls are forbidden. Adds to the chaining documentation page that such "split" calls are not allowed. * chore: Refresh this PR to reflect underlying changes Also addresses the review request with a detailed comment on the correctness of a new code section. Note that it reverts some changes to the TypeScript signatures of the matrix functions ones() and zeros() -- they do not actually have a typed-function signature of two numbers and an optional format specifically for two dimensions. What they have is a single rest parameter, from which the format is extracted if present. Hence, due to the ban on breaking rest parameters, it is not valid to call math.chain(3).zeros(2) to make a 3-by-2 matrix of zeros, which seems like a perfectly valid ban as the division of the dimensions is very confusing; this should be written as math.chain([3,2]).zeros(). The TypeScript signatures are fixed accordingly, along with the edge case of no arguments to ones() and zeros() at all, which does work to produce the "empty matrix". * Unit test `typeOf` on the minified bundle (currently failing) * Update AUTHORS * Improve testing of typeOf on browser bundle (WIP) * fix #2621: Module "mathjs" has no exported member "count" .ts(2305) (#2622) * fix #2621: Module "mathjs" has no exported member "count" .ts(2305) * feat: Update comments of count * feat: update the signature for count * feat: add usage example for count and sum * chore: Ensure type info remains in bundling Co-authored-by: Glen Whitney Co-authored-by: Chris Chudzicki Co-authored-by: Hansuku <1556207795@qq.com> --- AUTHORS | 2 + README.md | 2 +- docs/core/chaining.md | 8 + docs/datatypes/units.md | 4 + docs/expressions/algebra.md | 6 + docs/expressions/syntax.md | 46 +- package-lock.json | 16 +- package.json | 5 +- src/core/create.js | 3 + src/core/function/import.js | 13 +- src/core/function/typed.js | 62 +- src/expression/embeddedDocs/embeddedDocs.js | 2 + .../function/algebra/simplifyConstant.js | 16 + .../function/algebra/simplifyCore.js | 2 +- src/expression/node/AccessorNode.js | 323 ++++--- src/expression/node/ArrayNode.js | 295 ++++--- src/expression/node/AssignmentNode.js | 500 ++++++----- src/expression/node/BlockNode.js | 296 +++---- src/expression/node/ConditionalNode.js | 419 ++++----- src/expression/node/ConstantNode.js | 290 +++--- src/expression/node/FunctionAssignmentNode.js | 381 ++++---- src/expression/node/FunctionNode.js | 793 ++++++++--------- src/expression/node/IndexNode.js | 395 ++++----- src/expression/node/Node.js | 636 +++++++------- src/expression/node/ObjectNode.js | 311 +++---- src/expression/node/OperatorNode.js | 794 +++++++++-------- src/expression/node/ParenthesisNode.js | 246 +++--- src/expression/node/RangeNode.js | 448 +++++----- src/expression/node/RelationalNode.js | 371 ++++---- src/expression/node/SymbolNode.js | 320 +++---- src/expression/operators.js | 103 ++- src/expression/parse.js | 23 +- src/expression/transform/std.transform.js | 6 +- src/factoriesAny.js | 1 + src/factoriesNumber.js | 1 + src/function/algebra/derivative.js | 45 +- src/function/algebra/leafCount.js | 3 - src/function/algebra/rationalize.js | 183 ++-- src/function/algebra/resolve.js | 43 +- src/function/algebra/simplify.js | 164 ++-- .../{simplify => }/simplifyConstant.js | 48 +- src/function/algebra/simplifyCore.js | 260 ++++-- src/function/algebra/symbolicEqual.js | 34 +- src/function/arithmetic/abs.js | 22 +- src/function/arithmetic/add.js | 105 +-- src/function/arithmetic/addScalar.js | 15 +- src/function/arithmetic/cbrt.js | 18 +- src/function/arithmetic/ceil.js | 68 +- src/function/arithmetic/cube.js | 15 +- src/function/arithmetic/divide.js | 14 +- src/function/arithmetic/divideScalar.js | 24 +- src/function/arithmetic/dotDivide.js | 95 +- src/function/arithmetic/dotMultiply.js | 85 +- src/function/arithmetic/dotPow.js | 100 +-- src/function/arithmetic/exp.js | 20 +- src/function/arithmetic/expm1.js | 20 +- src/function/arithmetic/fix.js | 48 +- src/function/arithmetic/floor.js | 68 +- src/function/arithmetic/gcd.js | 117 +-- src/function/arithmetic/hypot.js | 12 +- src/function/arithmetic/lcm.js | 115 +-- src/function/arithmetic/log.js | 18 +- src/function/arithmetic/log10.js | 4 +- src/function/arithmetic/log1p.js | 10 +- src/function/arithmetic/log2.js | 4 +- src/function/arithmetic/mod.js | 126 +-- src/function/arithmetic/multiply.js | 51 +- src/function/arithmetic/multiplyScalar.js | 17 +- src/function/arithmetic/norm.js | 7 - src/function/arithmetic/nthRoot.js | 189 ++-- src/function/arithmetic/round.js | 54 +- src/function/arithmetic/sign.js | 12 +- src/function/arithmetic/sqrt.js | 14 +- src/function/arithmetic/square.js | 16 +- src/function/arithmetic/subtract.js | 176 ++-- src/function/arithmetic/unaryMinus.js | 24 +- src/function/arithmetic/unaryPlus.js | 6 +- src/function/bitwise/bitAnd.js | 93 +- src/function/bitwise/bitNot.js | 6 +- src/function/bitwise/bitOr.js | 93 +- src/function/bitwise/bitXor.js | 93 +- src/function/bitwise/leftShift.js | 153 ++-- src/function/bitwise/rightArithShift.js | 153 ++-- src/function/bitwise/rightLogShift.js | 153 ++-- .../bitwise/useMatrixForArrayScalar.js | 15 + src/function/complex/arg.js | 4 +- src/function/complex/conj.js | 18 +- src/function/complex/im.js | 23 +- src/function/complex/re.js | 22 +- src/function/logical/and.js | 178 ++-- src/function/logical/not.js | 10 +- src/function/logical/or.js | 105 +-- src/function/logical/xor.js | 105 +-- src/function/matrix/diff.js | 10 +- src/function/matrix/ones.js | 3 +- src/function/matrix/sqrtm.js | 6 +- src/function/matrix/transpose.js | 89 +- src/function/matrix/zeros.js | 3 +- src/function/probability/factorial.js | 4 +- src/function/probability/gamma.js | 93 +- src/function/probability/kldivergence.js | 6 +- src/function/probability/lgamma.js | 55 +- src/function/relational/compare.js | 135 +-- src/function/relational/compareNatural.js | 108 +-- src/function/relational/compareText.js | 61 +- src/function/relational/compareUnits.js | 12 + src/function/relational/equal.js | 97 +- src/function/relational/equalScalar.js | 12 +- src/function/relational/larger.js | 119 +-- src/function/relational/largerEq.js | 119 +-- src/function/relational/smaller.js | 119 +-- src/function/relational/smallerEq.js | 119 +-- src/function/relational/unequal.js | 97 +- src/function/special/erf.js | 4 +- src/function/statistics/std.js | 13 +- src/function/trigonometry/acos.js | 12 +- src/function/trigonometry/acosh.js | 9 +- src/function/trigonometry/acot.js | 12 +- src/function/trigonometry/acoth.js | 12 +- src/function/trigonometry/acsc.js | 12 +- src/function/trigonometry/acsch.js | 12 +- src/function/trigonometry/asec.js | 12 +- src/function/trigonometry/asech.js | 12 +- src/function/trigonometry/asin.js | 13 +- src/function/trigonometry/asinh.js | 13 +- src/function/trigonometry/atan.js | 13 +- src/function/trigonometry/atan2.js | 108 +-- src/function/trigonometry/atanh.js | 13 +- src/function/trigonometry/cos.js | 33 +- src/function/trigonometry/cosh.js | 28 +- src/function/trigonometry/cot.js | 30 +- src/function/trigonometry/coth.js | 29 +- src/function/trigonometry/csc.js | 34 +- src/function/trigonometry/csch.js | 29 +- src/function/trigonometry/sec.js | 34 +- src/function/trigonometry/sech.js | 29 +- src/function/trigonometry/sin.js | 34 +- src/function/trigonometry/sinh.js | 29 +- src/function/trigonometry/tan.js | 34 +- src/function/trigonometry/tanh.js | 29 +- src/function/trigonometry/trigUnit.js | 12 + src/function/unit/to.js | 56 +- src/function/utils/hasNumericValue.js | 2 + src/function/utils/isInteger.js | 4 +- src/function/utils/isNegative.js | 9 +- src/function/utils/isNumeric.js | 14 +- src/function/utils/isPositive.js | 9 +- src/function/utils/isPrime.js | 4 +- src/function/utils/isZero.js | 9 +- src/function/utils/numeric.js | 11 +- src/function/utils/typeOf.js | 82 +- src/type/bignumber/function/bignumber.js | 4 +- src/type/boolean.js | 4 +- src/type/chain/Chain.js | 23 +- src/type/complex/Complex.js | 2 + src/type/complex/function/complex.js | 4 +- src/type/fraction/Fraction.js | 2 + src/type/fraction/function/fraction.js | 4 +- src/type/matrix/DenseMatrix.js | 13 +- src/type/matrix/SparseMatrix.js | 6 + src/type/matrix/utils/README.md | 33 +- .../{algorithm01.js => matAlgo01xDSid.js} | 4 +- .../{algorithm02.js => matAlgo02xDS0.js} | 6 +- .../{algorithm03.js => matAlgo03xDSf.js} | 6 +- .../{algorithm04.js => matAlgo04xSidSid.js} | 10 +- .../{algorithm05.js => matAlgo05xSfSf.js} | 6 +- .../{algorithm06.js => matAlgo06xS0S0.js} | 6 +- .../{algorithm07.js => matAlgo07xSSf.js} | 6 +- .../{algorithm08.js => matAlgo08xS0Sid.js} | 8 +- .../{algorithm09.js => matAlgo09xS0Sf.js} | 6 +- .../{algorithm10.js => matAlgo10xSids.js} | 6 +- .../{algorithm11.js => matAlgo11xS0s.js} | 6 +- .../{algorithm12.js => matAlgo12xSfs.js} | 6 +- .../utils/{algorithm13.js => matAlgo13xDD.js} | 6 +- .../utils/{algorithm14.js => matAlgo14xDs.js} | 6 +- src/type/matrix/utils/matrixAlgorithmSuite.js | 169 ++++ src/type/number.js | 4 +- src/type/string.js | 4 +- src/type/unit/Unit.js | 66 +- src/type/unit/function/unit.js | 9 +- src/utils/is.js | 45 +- src/utils/snapshot.js | 72 +- test/benchmark/load.js | 5 +- .../browser-test-config/browserstack-karma.js | 1 - test/node-tests/browser.test.js | 34 + test/node-tests/doc.test.js | 4 +- .../treeShaking/treeShaking.test.js | 2 +- test/unit-tests/core/typed.test.js | 10 + .../expression/node/AccessorNode.test.js | 2 +- .../expression/node/ArrayNode.test.js | 2 +- .../expression/node/AssignmentNode.test.js | 3 +- .../expression/node/BlockNode.test.js | 2 +- .../expression/node/ConditionalNode.test.js | 2 +- .../expression/node/ConstantNode.test.js | 2 +- .../node/FunctionAssignmentNode.test.js | 3 +- .../expression/node/FunctionNode.test.js | 2 +- .../expression/node/IndexNode.test.js | 2 +- test/unit-tests/expression/node/Node.test.js | 4 +- .../expression/node/ObjectNode.test.js | 17 +- .../expression/node/OperatorNode.test.js | 825 ++++++++---------- .../expression/node/ParenthesisNode.test.js | 2 +- .../expression/node/RangeNode.test.js | 2 +- .../expression/node/RelationalNode.test.js | 2 +- .../expression/node/SymbolNode.test.js | 2 +- test/unit-tests/expression/operators.test.js | 13 +- test/unit-tests/expression/parse.test.js | 31 +- .../function/algebra/derivative.test.js | 12 +- .../function/algebra/resolve.test.js | 44 +- .../function/algebra/simplify.test.js | 37 +- .../function/algebra/simplifyConstant.test.js | 38 + .../function/algebra/simplifyCore.test.js | 57 +- .../function/arithmetic/cbrt.test.js | 10 +- .../function/arithmetic/cube.test.js | 12 +- .../function/arithmetic/exp.test.js | 14 +- .../function/arithmetic/expm1.test.js | 15 +- .../function/arithmetic/log.test.js | 9 +- .../function/arithmetic/norm.test.js | 5 +- .../function/arithmetic/sqrt.test.js | 9 +- .../function/arithmetic/square.test.js | 9 +- test/unit-tests/function/logical/not.test.js | 5 +- test/unit-tests/function/matrix/count.test.js | 2 +- test/unit-tests/function/matrix/diag.test.js | 2 +- test/unit-tests/function/matrix/diff.test.js | 16 +- test/unit-tests/function/matrix/sqrtm.test.js | 8 +- .../function/matrix/transpose.test.js | 2 +- .../function/probability/gamma.test.js | 10 +- .../function/set/setCartesian.test.js | 5 +- .../function/set/setDifference.test.js | 5 +- .../function/set/setDistinct.test.js | 4 +- .../function/set/setIntersect.test.js | 5 +- .../function/set/setSymDifference.test.js | 5 +- test/unit-tests/function/set/setUnion.test.js | 4 +- .../function/trigonometry/acos.test.js | 7 +- .../function/trigonometry/acosh.test.js | 8 +- .../function/trigonometry/acot.test.js | 8 +- .../function/trigonometry/acoth.test.js | 8 +- .../function/trigonometry/acsc.test.js | 8 +- .../function/trigonometry/acsch.test.js | 8 +- .../function/trigonometry/asec.test.js | 8 +- .../function/trigonometry/asech.test.js | 8 +- .../function/trigonometry/asin.test.js | 8 +- .../function/trigonometry/asinh.test.js | 8 +- .../function/trigonometry/atan.test.js | 8 +- .../function/trigonometry/atanh.test.js | 8 +- .../function/trigonometry/cos.test.js | 10 +- .../function/trigonometry/cosh.test.js | 20 +- .../function/trigonometry/cot.test.js | 10 +- .../function/trigonometry/coth.test.js | 20 +- .../function/trigonometry/csc.test.js | 10 +- .../function/trigonometry/csch.test.js | 20 +- .../function/trigonometry/sec.test.js | 10 +- .../function/trigonometry/sech.test.js | 20 +- .../function/trigonometry/sin.test.js | 10 +- .../function/trigonometry/sinh.test.js | 20 +- .../function/trigonometry/tan.test.js | 10 +- .../function/trigonometry/tanh.test.js | 20 +- test/unit-tests/function/unit/to.test.js | 2 +- .../function/utils/hasNumericValue.test.js | 2 + test/unit-tests/function/utils/typeof.test.js | 8 +- test/unit-tests/type/chain/Chain.test.js | 9 + .../type/matrix/function/matrix.test.js | 4 +- .../type/matrix/function/sparse.test.js | 2 +- test/unit-tests/type/numeric.test.js | 6 +- test/unit-tests/type/unit/Unit.test.js | 5 + types/index.d.ts | 326 ++++--- types/index.ts | 190 ++-- 266 files changed, 7313 insertions(+), 8339 deletions(-) create mode 100644 src/expression/embeddedDocs/function/algebra/simplifyConstant.js rename src/function/algebra/{simplify => }/simplifyConstant.js (90%) create mode 100644 src/function/bitwise/useMatrixForArrayScalar.js create mode 100644 src/function/relational/compareUnits.js create mode 100644 src/function/trigonometry/trigUnit.js rename src/type/matrix/utils/{algorithm01.js => matAlgo01xDSid.js} (96%) rename src/type/matrix/utils/{algorithm02.js => matAlgo02xDS0.js} (93%) rename src/type/matrix/utils/{algorithm03.js => matAlgo03xDSf.js} (94%) rename src/type/matrix/utils/{algorithm04.js => matAlgo04xSidSid.js} (93%) rename src/type/matrix/utils/{algorithm05.js => matAlgo05xSfSf.js} (95%) rename src/type/matrix/utils/{algorithm06.js => matAlgo06xS0S0.js} (95%) rename src/type/matrix/utils/{algorithm07.js => matAlgo07xSSf.js} (94%) rename src/type/matrix/utils/{algorithm08.js => matAlgo08xS0Sid.js} (93%) rename src/type/matrix/utils/{algorithm09.js => matAlgo09xS0Sf.js} (94%) rename src/type/matrix/utils/{algorithm10.js => matAlgo10xSids.js} (92%) rename src/type/matrix/utils/{algorithm11.js => matAlgo11xS0s.js} (93%) rename src/type/matrix/utils/{algorithm12.js => matAlgo12xSfs.js} (93%) rename src/type/matrix/utils/{algorithm13.js => matAlgo13xDD.js} (93%) rename src/type/matrix/utils/{algorithm14.js => matAlgo14xDs.js} (91%) create mode 100644 src/type/matrix/utils/matrixAlgorithmSuite.js create mode 100644 test/unit-tests/function/algebra/simplifyConstant.test.js 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() }