diff --git a/docs/datatypes/matrices.md b/docs/datatypes/matrices.md index 01178f7413..cb0803f226 100644 --- a/docs/datatypes/matrices.md +++ b/docs/datatypes/matrices.md @@ -3,9 +3,11 @@ Math.js supports multidimensional matrices and arrays. Matrices can be created, manipulated, and used in calculations. Both regular JavaScript arrays and the matrix type implemented by math.js can be used -interchangeably in all relevant math.js functions. math.js supports both -dense and sparse matrices. - +interchangeably in all relevant math.js functions. math.js supports dense +matrices (in which each entry is stored in memory), sparse matrices (in +which only the non-zero entries are stored, with information about the +indices at which they occur), and "Range" matrices whose entries are generated +on the fly by arithmetic sequences. ## Arrays and matrices @@ -13,13 +15,15 @@ Math.js supports two types of matrices: - `Array`, a regular JavaScript array. A multidimensional array can be created by nesting arrays. -- `Matrix`, a matrix implementation by math.js. A `Matrix` is an object wrapped - around a regular JavaScript `Array`, providing utility functions for easy - matrix manipulation such as `subset`, `size`, `resize`, `clone`, and more. +- `Matrix`, a matrix implementation by math.js. A `Matrix` is an object that + provides utility functions for easy matrix manipulation such as `subset`, + `size`, `resize`, `clone`, and more. There are multiple concrete + implementations for this `Matrix` api (see below); for example, the + `DenseMatrix` is a wrapper around a possibly multidimensional `Array`. In most cases, the type of matrix output from functions is determined by the -function input: An `Array` as input will return an `Array`, a `Matrix` as input -will return a `Matrix`. In case of mixed input, a `Matrix` is returned. +function input: An `Array` as input will return an `Array` and a `Matrix` as +input will return a `Matrix`. In case of mixed input, a `Matrix` is returned. For functions where the type of output cannot be determined from the input, the output is determined by the configuration option `matrix`, which can be a string `'Matrix'` (default) or `'Array'`. The function `size` is @@ -124,6 +128,16 @@ math.range(0, 8, 2) // [0, 2, 4, 6] math.range(3, -1, -1) // [3, 2, 1, 0] ``` +A range can also be created by passing a plain object of attributes, with any +or all of the properties `start`, `step`, `length` (number of entries), +`end` (exclusive limit), or `last` (inclusive limit). For example, +```js +math.range({start: 2, length: 3}) // [2, 3, 4] +math.range({start: math.fraction(0), last: math.fraction(1), length: 4}) + // Fractions [0, 1/3, 2/3, 1] +math.range({start: zeros(3), step: [1, 2, 3], length: 3}) + // [[0, 0, 0], [1, 2, 3], [2, 4, 6]] +``` ## Calculations @@ -270,7 +284,10 @@ Matrices have a `subset` function, which is applied to the matrix itself: `Matrix.subset(index [, replacement])`. For both matrices and arrays, the static function `subset(matrix, index [, replacement])` can be used. When parameter `replacement` is provided, the function will replace a subset -in the matrix, and if not, a subset of the matrix will be returned. +in the matrix, and if not, a subset of the matrix will be returned. Note that +Ranges are immutable (since their entries are defined by a specific +mathematical relation) and hence only allow their subsets to be read, not +replaced. A subset can be defined using an `Index`. An `Index` contains a single value or a set of values for each dimension of a matrix. An `Index` can be @@ -370,7 +387,7 @@ const m = math.matrix([[1, 2, 3], [4, 5, 6]]) There are two methods available on matrices that allow to get or set a single value inside a matrix. It is important to note that the `set` method will -mutate the matrix. +mutate the matrix (and so is disallowed on Ranges). ```js const p = math.matrix([[1, 2], [3, 4]]) @@ -504,10 +521,18 @@ At this moment `forEach` doesn't include the same functionality. Math.js supports both dense matrices and sparse matrices. Sparse matrices are efficient for matrices largely containing zeros. In that case they save a lot of memory, and calculations can be much faster than for dense matrices. -Math.js supports two type of matrices: +Math.js supports three types of matrices: -- Dense matrix (`'dense'`, `default`) A regular, dense matrix, supporting multidimensional matrices. This is the default matrix type. +- Dense matrix (`'dense'`, `default`) A regular, dense matrix, supporting + multidimensional matrices. This is the default matrix type. - Sparse matrix (`'sparse'`): A two dimensional sparse matrix implementation. +- Range ('range'): A matrix that has entries specified by an arithmetic + sequence. Note that it is possible for a Range to be multidimensional, e.g. + via +```js +const r2d = math.range([1, 11, 21], [4, 14, 24], [1, 1, 1]) +// returns a Range representing [[1, 11, 21], [2, 12, 22], [3, 13, 23]] +``` The type of matrix can be selected when creating a matrix using the construction functions `matrix`, `diag`, `identity`, `ones`, and `zeros`. @@ -515,6 +540,8 @@ The type of matrix can be selected when creating a matrix using the construction // create sparse matrices const m1 = math.matrix([[0, 1], [0, 0]], 'sparse') const m2 = math.identity(1000, 1000, 'sparse') +// create a range matrix +const m3 = math.ones(3, 4, 'range') ``` You can also coerce an array or matrix into sparse storage format with the @@ -523,7 +550,6 @@ You can also coerce an array or matrix into sparse storage format with the const md = math.matrix([[0, 1], [0,0]]) // dense const ms = math.sparse(md) // sparse ``` - Caution: `sparse` called on a JavaScript array of _n_ plain numbers produces a matrix with one column and _n_ rows -- in contrast to `matrix`, which produces a 1-dimensional matrix object with _n_ entries, i.e., a vector @@ -534,6 +560,13 @@ const mv = math.matrix([0, 0, 1]) // Has size [3] const mc = math.sparse([0, 0, 1]) // A "column vector," has size [3, 1] ``` +And you can create Ranges directly with the `range` function. +```js +const mr = math.range(2.5, 8.5, 1.5) // Range [2.5, 4, 5.5, 7] +const mr = math.range({start: 3n, step: 2n, length: 3}) + // Range [3n, 5n, 7n] +``` + ## API All relevant functions in math.js support Matrices and Arrays. Functions like `math.add` and `math.subtract`, `math.sqrt` handle matrices element wise. There is a set of functions specifically for creating or manipulating matrices, such as: @@ -542,9 +575,17 @@ All relevant functions in math.js support Matrices and Arrays. Functions like `m - Functions like `math.subset` and `math.index` to get or replace a part of a matrix - Functions like `math.transpose` and `math.diag` to manipulate matrices. -A full list of matrix functions is available on the [functions reference page](../reference/functions.md#matrix-functions). +A full list of matrix functions is available on the +[functions reference page](../reference/functions.md#matrix-functions). + +The common `Matrix` interface implemented by all Matrix classes has its own +[documentation page](../reference/classes/matrix.md). -Two types of matrix classes are available in math.js, for storage of dense and sparse matrices. Although they contain public functions documented as follows, using the following API directly is *not* recommended. Prefer using the functions in the "math" namespace wherever possible. +Three types of Matrix classes are available in math.js, for storage of dense, +sparse, and range matrices. Although they contain public functions documented +as follows, using the following APIs directly is *not* recommended. Prefer +using the functions in the "math" namespace wherever possible. - [DenseMatrix](../reference/classes/densematrix.md) - [SparseMatrix](../reference/classes/sparsematrix.md) +- [Range](../reference/classes/range.md) diff --git a/docs/deprecation_status.md b/docs/deprecation_status.md new file mode 100644 index 0000000000..992360bffc --- /dev/null +++ b/docs/deprecation_status.md @@ -0,0 +1,32 @@ +# Deprecated and discontinued features + +On rare occasions, to make the design of mathjs more systematic or to smoothly +accommodate new features, it is necessary to remove previous features. In such +cases, there is generally first discussion on the +[Github repository](https://github.com/josdejong/mathjs) about the need for +deprecation. Then in some release (most likely a major-version release), the +feature is placed into a deprecated state, in which it still works, typically +exactly as it had worked, but a JavaScript console +warning is issued when the feature is used. Finally, no sooner than the second +major-version release later, the feature is discontinued, meaning that it no +longer functions, although attempts to use it _may_ still throw a JavaScript +error giving information about the prior functionality. + +All documented features not listed on this page are not deprecated and may be +relied upon to continue operating for at least three major versions. +Undocumented features are subject to change on any release and should not be +relied upon. + +## Currently deprecated features + +|Feature type | Feature| First deprecated in|Comments| +|-------------|--------|--------------------|--------| +|Configuration option|`config.compatibility.subset`|v16.0.0|reverts a breaking change to the behavior of `math.subset()` implemented in v15.0.0| +|Library function |`math.apply()` |v14.2.0|use synonymous `math.mapSlices()` instead| +|Class method |`Range.parse()` |v16.0.0|use library function `math.parse()` instead| + +## Discontinued features + +|Feature type|Feature|First deprecated in|Discontinued as of| +|------------|-------|-------------------|------------------| +|Configuration option|`config.epsilon`|v13.0.0|v16.0.0 | diff --git a/docs/expressions/syntax.md b/docs/expressions/syntax.md index 2b8b39d8cc..060ec96697 100644 --- a/docs/expressions/syntax.md +++ b/docs/expressions/syntax.md @@ -14,13 +14,15 @@ the lower level syntax of math.js. Differences are: - No need to prefix functions and constants with the `math.*` namespace, you can just enter `sin(pi / 4)`. -- Matrix indexes are one-based instead of zero-based. +- By default, bracket notation `[1, 2, 3]` produces a Matrix object rather + than an Array; and an Array can be written with list notation, as `(1, 2, 3)`. +- Matrix and Array indexes are one-based instead of zero-based. - There are index and range operators which allow more conveniently getting and setting matrix indexes, like `A[2:4, 1]`. - Both indexes and ranges and have the upper-bound included. - There is a differing syntax for defining functions. Example: `f(x) = x^2`. - There are custom operators like `x + y` instead of `add(x, y)`. -- Some operators are different. For example `^` is used for exponentiation, +- Some operators are different. For example, `^` is used for exponentiation, not bitwise xor. - Implicit multiplication, like `2 pi`, is supported and has special rules. - Relational operators (`<`, `>`, `<=`, `>=`, `==`, and `!=`) are chained, so the expression `5 < x < 10` is equivalent to `5 < x and x < 10`. @@ -58,7 +60,8 @@ Functions below. Operator | Name | Syntax | Associativity | Example | Result ----------- |-----------------------------|-------------| ------------- |-----------------------| --------------- `(`, `)` | Grouping | `(x)` | None | `2 * (3 + 4)` | `14` -`[`, `]` | Matrix, Index | `[...]` | None | `[[1,2],[3,4]]` | `[[1,2],[3,4]]` + | Array, function arguments | `(x, y,...)`| None | `((), (1,), (1,2))` | `[[], [1], [1,2]]` +`[`, `]` | Matrix, Index | `[...]` | None | `[[1,2],[3,4]]` | `matrix([[1,2],[3,4]])` `{`, `}` | Object | `{...}` | None | `{a: 1, b: 2}` | `{a: 1, b: 2}` `,` | Parameter separator | `x, y` | Left to right | `max(2, 1, 5)` | `5` `.` | Property accessor | `obj.prop` | Left to right | `obj={a: 12}; obj.a` | `12` @@ -112,7 +115,7 @@ The operators have the following precedence, from highest to lowest: Operators | Description --------------------------------- | -------------------- -`(...)`
`[...]`
`{...}` | Grouping
Matrix
Object +`(...)`
`[...]`
`{...}` | Grouping/Array
Matrix
Object `x(...)`
`x[...]`
`obj.prop`
`:`| Function call
Matrix index
Property accessor
Key/value separator `'` | Matrix transpose `!` | Factorial diff --git a/docs/index.md b/docs/index.md index 47e0340a56..89c9061997 100644 --- a/docs/index.md +++ b/docs/index.md @@ -38,3 +38,4 @@ Math.js can be used in the browser, in node.js and in any JavaScript engine. Ins - [Custom bundling](custom_bundling.md) - [Command Line Interface](command_line_interface.md) - [History](../HISTORY.md) +- [Deprecation status](deprecation_status.md) of features diff --git a/docs/reference/classes/densematrix.md b/docs/reference/classes/densematrix.md index 8c9f3770ae..69e2ec441d 100644 --- a/docs/reference/classes/densematrix.md +++ b/docs/reference/classes/densematrix.md @@ -1,247 +1,87 @@ ## DenseMatrix -Dense Matrix implementation. This type implements an efficient Array format -for dense matrices. - -* _instance_ - * [.storage()](#DenseMatrix+storage) ⇒ string - * [.datatype()](#DenseMatrix+datatype) ⇒ string - * [.create(data, [datatype])](#DenseMatrix+create) - * [.subset(index, [replacement], [defaultValue])](#DenseMatrix+subset) - * [.get(index)](#DenseMatrix+get) ⇒ \* - * [.set(index, value, [defaultValue])](#DenseMatrix+set) ⇒ DenseMatrix - * [.resize(size, [defaultValue], [copy])](#DenseMatrix+resize) ⇒ Matrix - * [.clone()](#DenseMatrix+clone) ⇒ DenseMatrix - * [.size()](#DenseMatrix+size) ⇒ Array.<number> - * [.map(callback)](#DenseMatrix+map) ⇒ DenseMatrix - * [.forEach(callback)](#DenseMatrix+forEach) - * [.toArray()](#DenseMatrix+toArray) ⇒ Array - * [.valueOf()](#DenseMatrix+valueOf) ⇒ Array - * [.format([options])](#DenseMatrix+format) ⇒ string - * [.toString()](#DenseMatrix+toString) ⇒ string - * [.toJSON()](#DenseMatrix+toJSON) ⇒ Object - * [.diagonal([k])](#DenseMatrix+diagonal) ⇒ Array - * [.swapRows(i, j)](#DenseMatrix+swapRows) ⇒ Matrix -* _static_ - * [.diagonal(size, value, [k], [defaultValue])](#DenseMatrix.diagonal) ⇒ DenseMatrix - * [.fromJSON(json)](#DenseMatrix.fromJSON) ⇒ DenseMatrix - * [.preprocess(data)](#DenseMatrix.preprocess) ⇒ Array - - -### denseMatrix.storage() ⇒ string -Get the storage format used by the matrix. - -Usage: - -```js -const format = matrix.storage() // retrieve storage format -``` - -**Kind**: instance method of DenseMatrix -**Returns**: string - The storage format. - -### denseMatrix.datatype() ⇒ string -Get the datatype of the data stored in the matrix. - -Usage: - -```js -const format = matrix.datatype() // retrieve matrix datatype -``` - -**Kind**: instance method of DenseMatrix -**Returns**: string - The datatype. - -### denseMatrix.create(data, [datatype]) -Create a new DenseMatrix - -**Kind**: instance method of DenseMatrix - -| Param | Type | -| --- | --- | -| data | Array | -| [datatype] | string | - - -### denseMatrix.subset(index, [replacement], [defaultValue]) -Get a subset of the matrix, or replace a subset of the matrix. - -Usage: - -```js -const subset = matrix.subset(index) // retrieve subset -const value = matrix.subset(index, replacement) // replace subset -``` - -**Kind**: instance method of DenseMatrix - -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| index | Index | | | -| [replacement] | Array | DenseMatrix| \* | | | -| [defaultValue] | \* | 0 | Default value, filled in on new entries when the matrix is resized. If not provided, new matrix elements will be filled with zeros. | - - -### denseMatrix.get(index) ⇒ \* -Get a single element from the matrix. - -**Kind**: instance method of DenseMatrix -**Returns**: \* - value - -| Param | Type | Description | -| --- | --- | --- | -| index | Array.<number> | Zero-based index | - - -### denseMatrix.set(index, value, [defaultValue]) ⇒ DenseMatrix -Replace a single element in the matrix. - -**Kind**: instance method of DenseMatrix -**Returns**: DenseMatrix- self - -| Param | Type | Description | -| --- | --- | --- | -| index | Array.<number> | Zero-based index | -| value | \* | | -| [defaultValue] | \* | Default value, filled in on new entries when the matrix is resized. If not provided, new matrix elements will be left undefined. | - - -### denseMatrix.resize(size, [defaultValue], [copy]) ⇒ Matrix -Resize the matrix to the given size. Returns a copy of the matrix when -`copy=true`, otherwise return the matrix itself (resize in place). - -**Kind**: instance method of DenseMatrix -**Returns**: Matrix - The resized matrix - -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| size | Array.<number> | | The new size the matrix should have. | -| [defaultValue] | \* | 0 | Default value, filled in on new entries. If not provided, the matrix elements will be filled with zeros. | -| [copy] | boolean | | Return a resized copy of the matrix | - - -### denseMatrix.clone() ⇒ DenseMatrix -Create a clone of the matrix - -**Kind**: instance method of DenseMatrix -**Returns**: DenseMatrix- clone - -### denseMatrix.size() ⇒ Array.<number> -Retrieve the size of the matrix. - -**Kind**: instance method of DenseMatrix -**Returns**: Array.<number> - size - -### denseMatrix.map(callback) ⇒ DenseMatrix -Create a new matrix with the results of the callback function executed on -each entry of the matrix. - -**Kind**: instance method of DenseMatrix -**Returns**: DenseMatrix- matrix - -| Param | Type | Description | -| --- | --- | --- | -| callback | function | The callback function is invoked with three parameters: the value of the element, the index of the element, and the Matrix being traversed. | - - -### denseMatrix.forEach(callback) -Execute a callback function on each entry of the matrix. - -**Kind**: instance method of DenseMatrix - -| Param | Type | Description | -| --- | --- | --- | -| callback | function | The callback function is invoked with three parameters: the value of the element, the index of the element, and the Matrix being traversed. | - - -### denseMatrix.toArray() ⇒ Array -Create an Array with a copy of the data of the DenseMatrix - -**Kind**: instance method of DenseMatrix -**Returns**: Array - array - -### denseMatrix.valueOf() ⇒ Array -Get the primitive value of the DenseMatrix: a multidimensional array - -**Kind**: instance method of DenseMatrix -**Returns**: Array - array - -### denseMatrix.format([options]) ⇒ string -Get a string representation of the matrix, with optional formatting options. - -**Kind**: instance method of DenseMatrix -**Returns**: string - str - -| Param | Type | Description | -| --- | --- | --- | -| [options] | Object | number | function | Formatting options. See lib/utils/number:format for a description of the available options. | - - -### denseMatrix.toString() ⇒ string -Get a string representation of the matrix - -**Kind**: instance method of DenseMatrix -**Returns**: string - str - -### denseMatrix.toJSON() ⇒ Object -Get a JSON representation of the matrix - -**Kind**: instance method of DenseMatrix - -### denseMatrix.diagonal([k]) ⇒ Array -Get the kth Matrix diagonal. - -**Kind**: instance method of DenseMatrix -**Returns**: Array - The array vector with the diagonal values. - -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| [k] | number | BigNumber | 0 | The kth diagonal where the vector will retrieved. | - - -### denseMatrix.swapRows(i, j) ⇒ Matrix -Swap rows i and j in Matrix. - -**Kind**: instance method of DenseMatrix -**Returns**: Matrix - The matrix reference - -| Param | Type | Description | -| --- | --- | --- | -| i | number | Matrix row index 1 | -| j | number | Matrix row index 2 | - - -### DenseMatrix.diagonal(size, value, [k], [defaultValue]) ⇒ DenseMatrix -Create a diagonal matrix. - -**Kind**: static method of DenseMatrix - -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| size | Array | | The matrix size. | -| value | number | Array | | The values for the diagonal. | -| [k] | number | BigNumber | 0 | The kth diagonal where the vector will be filled in. | -| [defaultValue] | number | | The default value for non-diagonal | - -### DenseMatrix.fromJSON(json) ⇒ DenseMatrix -Generate a matrix from a JSON object +Implementation of the [Matrix](matrix.md) API as a wrapper around ordinary +(nested) JavaScript Arrays to hold the content. This representation is +reasonably efficient for "dense" matrices in which most entries are nonzero, +which may contain arbitrary data in any entry. -**Kind**: static method of DenseMatrix +This page documents only the differences and extensions to the basic Matrix +API. Any methods of that API not mentioned here operate exactly as documented +under [Matrix](matrix.md) -| Param | Type | Description | -| --- | --- | --- | -| json | Object | An object structured like `{"mathjs": "DenseMatrix", data: [], size: []}`, where mathjs is optional | +### Constructing a DenseMatrix - -### DenseMatrix.preprocess(data) ⇒ Array -Preprocess data, which can be an Array or DenseMatrix with nested Arrays and -Matrices. Replaces all nested Matrices with Arrays +#### new DenseMatrix(data? datatype?) -**Kind**: static method of DenseMatrix -**Returns**: Array - data +The _data_ should be a (nested, rectangular) Array or a Matrix. Creates a +DenseMatrix whose contents consist of a deep copy of the provided _data_. +Any supplied _datatype_ is trusted without checking. If it is not supplied, +the datatype is copied from the _data_ argument if it is a Matrix, and +otherwise it is simply left as `undefined`. If _data_ is unspecified, a +0-dimensional DenseMatrix is constructed. -| Param | Type | -| --- | --- | -| data | Array | +### Instance methods of Matrix +#### .storage() + +Always returns 'dense' + +#### .datatype() + +May be undefined, even if the entries are of uniform type, if no datatype +was specified at construction time. See `.getDataType()` for determining +a datatype from the data of the matrix itself. + +### New instance methods provided by DenseMatrix + +#### .getDataType() + +Examines the entries of the matrix to see if they are of uniform type, and +if so, returns the string mathjs name of that type. If not, returns "mixed". + +#### .rows() + +Returns a JavaScript Array of the rows of this Matrix, each as a 1-D +DenseMatrix. Throws an error if this Matrix is not 2-dimensional. + +#### .columns() + +Returns a JavaScript Array of the columns of this Matrix, each as a 1-D +DenseMatrix. Throws an error if this Matrix is not 2-dimensional. + +#### .diagonal(k?) + +When _k_ is not specified, returns the main diagonal of this Matrix, as +as a DenseMatrix. When it is, returns the diagonal _k_ entries above the +main diagonal when _k_ is positive, and the absolut value of _k_ entries +below the main diagonal when _k_ is negative. Note that these diagonals do +_not_ "wrap around", so in a an n×n matrix, `.diagonal(k)` will have n - |k| +entries. + +#### swapRows(i, j) + +Alters this Matrix in place by swapping rows _i_ and _j_. + +#### .toJSON() + +Returns a JSON (plain object) representation of this Matrix. This +representation can be restored to a DenseMatrix using the static `.fromJson` +method. + +### New static methods + +#### DenseMatrix.diagonal(size, diagonalValue?, k?, offDiagValue?) + +Constructs a fresh diagonal DenseMatrix of the given _size_. The diagonal +entries are along the main diagonal if _k_ is not specified or zero; otherwise +they are along an offset diagonal as per the _k_ parameter of the +`.diagonal` instance method. The diagonal entries are set to _diagonalValue_ +if it is specified, or one otherwise. The off-diagonal entries are set to +_offDiagValue_ if it is specified, or zero otherwise. Thus +`DenseMatrix.diagonal([3, 3])` returns the usual 3×3 identity matrix. + +#### DenseMatrix.fromJSON(json) + +Constructs a fresh DenseMatrix from _json_, which should be the result of +a `.toJSON()` call on some DenseMatrix. diff --git a/docs/reference/classes/matrix.md b/docs/reference/classes/matrix.md new file mode 100644 index 0000000000..d288a73e71 --- /dev/null +++ b/docs/reference/classes/matrix.md @@ -0,0 +1,236 @@ + +# Matrix API + +The mathjs library provides a number of classes that implement the Matrix API +described here, so that they can be used (at least to a certain extent) +interchangeably to represent rectangular arrays of numeric values, which may +be 1-dimensional (a vector of numbers, for example), 2-dimensional (ordinary +matrices), or 3-dimensional (rectangular solid blocks of numbers, in which +each "slice" is a matrix), or even higher-dimensional. + +Each of the implementing classes is subclass of the Matrix class, which +serves just to set the interface and generally speaking contains no +implementations of its own for the methods listed below. + +Every Matrix implementation should define a string "storage format", +describing the concrete aspects of that particular implementation. For example, +the SparseMatrix implementation that stores only the nonzero entries has +its storage format equal to "sparse". Each implementation should corresponde +to a single unique storage format and vice-versa. + +A Matrix can optionally have a datatype, which is the mathjs name of the +data type of every element stored in the Matrix. They are also allowed to +be heterogeneous, in which case the datatype should be "mixed", undefined, or +the empty string. + +## Instance methods of all Matrix objects + +### .storage() + +Returns the storage format of the Matrix, as a string. + +### .datatype() + +Returns the data type of the Matrix, as a string, or possibly undefined if +the Matrix is heterogeneous or if the data type is not necessarily known. + +### .clone() + +Returns a fresh "deep copy" of this Matrix. + +### .create(data, datatype?) + +Generates a fresh Matrix of the same storage format, containing the given +_data_ (which may be an ordinary JavaScript Array, possibly nested to represent +a multidimensional matrix, or another Matrix), and having the given _datatype_, +which may be omitted to infer the datatype from the _data_. + +### .size() + +Returns the extent of this matrix in each of its dimensions, as an ordinary +JavaScript Array of nonnegative numbers. + +Some examples: +``` +math.matrix([1, 2, 3]).size() // [3] +math.matrix([[1, 2, 3], [4, 5, 6]]) // [3, 2] +``` + +### .resize(size, defaultValue?, copy?) + +Changes the matrix size. If _copy_ is specified and true, leaves this Matrix +alone; otherwise alters it in place. In either case, the changed Matrix, of the +same storage type as this Matrix, is returned. If the new size is larger in any +dimension than the current size, new entries are filled in with the +_defaultValue_ or zero if it is not specified. If the new size is smaller +in any dimension, entries are dropped. (A combination is possible). Note that +if the new size has more entries than this Matrix has dimensions, this Matrix +is interpreted as lying along the **initial** dimensions of the resulting +matrix. (In the 1- to 2-dimensional case, that means this Matrix becomes +a **column**, not a row, of the result.) + +Some examples: +``` +math.matrix([[1, 4], [2, 5], [3, 6]]).resize([2, 3], -1) + // [[1, 4, -1], [2, 5, -1]] +math.matrix([1, 2, 3]).resize([4, 2]) + // [[1, 0], [2, 0], [3, 0], [0, 0]] +``` + +### .subset(index, replacement?, defaultValue?) + +Returns (and possibly replaces) a subarray of a Matrix. The _index_ must be +an instance of the mathjs [Index](matrixindex.md) type, specifying the extent +of the subarray in question along each dimension of the matrix. If none of +the optional arguments are specified, the corresponding subarray is simply +returned as a Matrix of the same storage format as this one. In this form, it +is an error if the index extends outside the bounds of this matrix. + +If _replacement_ and possibly also _defaultValue_ are specified, it is allowed +(but by no means necessary) for the _index_ to extend outside the bounds of the +matrix. If so, this matrix is first resized to the smallest rectangle that will +accommodate the index, filling in any new entries by the _defaultValue_ or +zero if that argument is not supplied. (So if the specified _index_ is within +the current bounds of this matrix, the _defaultValue_ argument, if any, is +simply ignored.) Then the subarray designated by the index is overwritten by +the _replacement_, potentially broadcasting the entries of the _replacement_ +if necessary to fill the entire designated subarray. Finally, this **entire** +matrix is returned (not just the replaced portion), unlike in the plain +access case. + +Some examples: +``` +const M = math.matrix([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 9, 8]]) +M.subset(math.index('1:2', '0:1')) // [[4, 5], [8, 9]] +M.subset(math.index('1:2', '0:1'), [[1, 2], [3, 4]]) + // [[0, 1, 2, 3], [1, 2, 6, 7], [3, 4, 9, 8]] +M.subset(math.index('0:1', '3:4'), [[9, 8], [7, 6]], -99) + // [[0, 1, 2, 9, 8], [1, 2, 6, 7, 6], [3, 4, 9, 8, -99]] +M.subset(math.index('1:2', '2:4'), 5) + // [[0, 1, 2, 9, 8], [1, 2, 5, 5, 5], [3, 4, 5, 5, 5]] +M.subset(math.index('1:2', '2:4'), [0, 1, 2]) + // [[0, 1, 2, 9, 8], [1, 2, 0, 1, 2], [3, 4, 0, 1, 2]] +M.subset(math.index('1:2', '2:4'), [[7], [6]]) + // [[0, 1, 2, 9, 8], [1, 2, 7, 7, 7], [3, 4, 6, 6, 6]] +``` + +### .layer(n) + +Returns the _n_ th "top-level" slice of this matrix as a Matrix of the same +type, or as the entry itself if this matrix is 1-dimensional. The argument _n_ +must be an integer number at least 0 and less than the extent of this matrix +in the first dimension. + +Some examples: +``` +math.matrix([1, 2, 3]).layer(1) // 2 +math.matrix([[1, 2], [3, 4], [6, 7]]).layer(1) // [3, 4] +math.matrix([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]).layer(1) // [[5, 6], [7, 8]] +``` + +### .get(position) + +Returns a single entry of this matrix at a specific _position_ given as +an array of numbers, each one the (0-based) index along the corresponding +dimension. An error will be thrown if the _position_ lies outside the bounds +of this matrix. + +Some examples: +``` +math.matrix([1, 2, 3]).get([2]) // 3 +math.matrix([[1, 2], [3, 4], [6, 7]]).get([1, 0]) // 3 +math.matrix([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]).get([0, 1, 0]) // 3 +``` + +### .set(position, value, defaultValue?) + +Replaces a single entry of this matrix and returns the entire matrix. In this +method, it is permissible for the position to lie outside the current bounds +of the matrix, in which case the matrix is first resized to the smallest +rectangular array including the position, using the _defaultValue_ or zero if +not specified for new entrues. + +Some examples: +``` +const M = math.matrix([1, 2, 3]) +M.set([2], 4) // [1, 2, 4] +M.set([0, 1], 8, -1) // [[1, 8], [2, -1], [4, -1]] +``` + +### .reshape(size, copy?) + +The entries of this Matrix in row-major order are inserted into +a changed Matrix having the specified _size_ in row-major order. Unlike with +`.resize`, the resulting Matrix is required to have exactly the same number +of entries as this Matrix. But like `.resize`, the current Matrix is altered +in place unless the the _copy_ argument is specified and true, in which +case this Matrix is left alone and the changed Matrix (of the same storage +format) is returned. + +Note that because of the rule on the number of entries, it is allowed to +specify (no more than) one of the extents of the new _size_ as -1, in which +case that extent is calculated from the others so that the number of entries +will match. + +Some examples: +``` +math.matrix([[1, 4], [2, 5], [3, 6]]).reshape([2, 3]) + // [[1, 4, 2], [5, 3, 6]] +math.matrix([1, 2, 3, 4, 5, 6, 7, 8]).reshape([2, -1]) + // [[1, 2, 3, 4], [5, 6, 7, 8]] +``` + +### .map(callback, skipZeros?) + +Returns a fresh matrix of the same size and storage type as this Matrix, in +which each entry has been replaced by the result of executing the _callback_ +with three arguments: the value of the entry, the position of the element as a +plain JavaScript Array of nonnegative numbers, and this Matrix being traversed. + +If the _skipZeros_ argument is specified and true, zero entries of this Matrix +are left alone (and the _callback_ is not executed for them). + +### .forEach(callback) + +Executes the _callback_ (with the same arguments as in `.map` for each +entry of this Matrix. + +### [Symbol.iterator] + +Implementations define a function on the special JavaScript iterator Symbol +so that the following syntax is supported: +``` +const M = math.matrix([[1, 2, 3], [4, 5, 6]]) +for (const {value, position} of M) { + console.log(`M[${position[0]}, ${position[1]}] = ${value}`) +} +``` +This example code will print out a listing of all of the entries of `M` in +row-major order (so in numerical order in this case). + +### .toArray(), .valueOf() + +These synonymous methods return the content of this Matrix as a JavaScript +Array, with nesting depth equal to the number of dimensions of this Matrix. + +### .format(options) + +Obtain a string representation of this Matrix. It accepts the same options as +the mathjs [`format` function](../functions/format.js). + +### .toString() + +Converts this Matrix to a string in a vanilla fashion (similar to the way +the corresponding nested Array would look). + +## Implementations + +The current concrete implementations of this interface are listed below, +with links to their specific documentation. + +* [DenseMatrix](densematrix.md) +* [SparseMatrix](sparsematrix.md) +* [Range](range.md) diff --git a/docs/reference/classes/range.md b/docs/reference/classes/range.md new file mode 100644 index 0000000000..6a584dd1c8 --- /dev/null +++ b/docs/reference/classes/range.md @@ -0,0 +1,187 @@ + + ## Range + +This type implements Matrix objects whose entries are given by arithmetic +sequences. Only the constants in the definition of the sequence are kept in +memory; the actual entries are generated on the fly as needed. This design +makes Range the most efficient in memory usage and often speed, for matrices +that happen to have entries in this form. Among Matrix implementations, only +Ranges can represent entities that are infinite in extent, with more +entries always available to be generated on demand. + +Note that the term "arithmetic sequence" does not imply that Ranges are +only (1-dimensional) vectors. If the a Range starts with, say [1, 2, 3], +and its step is 3 and its length is 4, then it represents the 2-dimensional +matrix `[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]`. + +Note that a Range matrix object is immutable, in the sense that its definition +and therefore its entries cannot be changed once the Range has been constructed. +Therefore, some methods of the [Matrix API](matrix.md) may throw errors if +they would entail in-place changes of a Range. Such exceptions are not +otherwise listed here. + +This page documents all other differeces and extensions to the basic Matrix +interface. + +### Constructing a Range + +Ranges have the most options for construction, representing the many possible +ways to define an arithmetic sequence. + +#### new Range(start?, end?, step?, options?) + +First, note that the options argument, which is distinguished as a plain +object with some of the keys listed below as "attributes" of a Range, may +appear at any point among the other constructor arguments. The other arguments +are convenience positional arguments, corresponding to the start, end, and +step attributes; they can be used instead of the equivalent options, but +the same attribute may not be specified in two different ways. Thus, +`new Range({end: 10}, 1)` and `new Range(1, {end: 10})` is OK, but +`new Range(1, {start: 1, end: 10})` is not. + +Every Range has several attributes that determine its entries. Once +constructed, these attributes cannot be changed; they are read-only. +Moreover, to allow different terminology that may be clearer in +different uses of this class, each attribute can be specified (at +construction time) or read (at any later time) via either of two +synonymous property names, separated by a vertical bar in the lists below. +Note that it is perfectly OK to specify an attribute using one of its +two names and read it later using the other; but the same attribute cannot +be specified in two different ways, even if the values match. + +Every Range has these attributes: + * start|from: the first element of the Range + * step|by: the step or common difference of the Range + * length|for: the number of elements in the Range. Note that this attribute + may be Infinity, so that a Range can represent an unending arithmetic + progression. + +In addition, a Range may have one or both of the following attributes: + * last|to: the inclusive final limit of the Range. This value must be + of the form `start + t * step` for some number `t`, in which case the + Range consists of `start + s * step` for all nonnegative integers `s ≤ t`. + * end|til: an exclusive limit of the Range. This value must be of the + form `start + u * step` for some number `u`, in which case the Range + consists of `start + s * step` for all nonnegative integers `s < u`. + +There is a consistency relation, in that if the last value is the start +value plus `t` times the step value, then the length must be the floor +of `t`, plus one. Similarly, if the end value is the start value plus `u` +times the step, then the length must be the ceiling of u. If the step +of a Range is zero, then it generally does not have an end or last value +to avoid breaking this consistency relation. + +A Range can be constructed from a plain object with any of the above +attributes, presuming they are consistent. Other positional arguments, if any, +are interprete as described above. + +Because of the consistency relation and defaults provided for convenience, +some or even all of the attributes may be missing in the constructor. +If any are missing, they are deduced for you in the following order: + * step|by: filled in via consistency if start, length, and at least + one of last and end are specified; otherwise set to the "one" value of + the type of last, end, or start if specified, or the number 1 if not. + * start|from: filled in via consistency with the step if length and at + least one of last and end are specified; otherwise set to the "zero" + value of the type of last or end if specified, or the number 0 if not. + * length|for: filled in via consistency with start and step if at least + one of last and end are specified; otherwise, set to 0. + +In addition, if the length value is finite and the step is nonzero, the +following are set whether or not they were specified, to canonicalize +the attributes of the Range (which makes it easier to use and interpret): + * last|to: Set to the start value plus the step times the length minus one. + * end|til: Set to the start value plus the step times the length. + +Note that the endpoints and increment may be specified with any type +handled by mathjs, but they must support the operations needed by Range +(addition, multiplication by an integer ordinary number, comparison). +The data type of the range is the data type of `start + n*step`, where `n` +is an integer number; the Range class assumes that this data type does not +depend on the value of `n`. + +#### [DEPRECATED] Range.parse(str) + +This static function generates Range objects from strings using the syntax +(`from:last` or `from:by:last`) of the mathjs expression language. It has +been deprecated in favor of calling `math.evaluate()` directly. + +### Instance methods of Matrix + +#### .storage() + +Always returns 'range'. + +#### .datatype() + +Always deduced via `math.typeOf` on the expression `start + 1 * step`. + +#### .create() + +Note that this function can be given a data array, and will attempt +to find attributes that will regenerate the array. It will throw an +error if the entries of the input do not form an arithmetic sequence. + +#### .layer(s) + +Note that this is the `s`-th element of the arithmetic sequnce, and it +has a simple formula: it is `start` plus `s * step`. + +### New instance methods of Range + +#### .getDataType() + +Purely for conssistency with the other Matrix implmentations, this method +is just a synonym for `.datatype` + +#### .createRange() + +A companion to the `.create()` method that takes exactly the parameters of +the constructor, rather than the specific parameters enforced on the +`.create()` method by virtue of the Matrix API. + +#### .min() + +Returns the smallest element of the sequence. If there is none because +the sequence has zero length or is infinite and decreasing, returns undefined. +If the elements of the sequence are not comparable, throws an error. + +#### .max() + +Returns the largest element of the sequence. If there is none because +the sequence has zero length or is infinite and increasing, returns undefined. +If the elements of the sequence are not comparable, throws an error. + +#### .rows() + +Returns a JavaScript Array of the rows of this Matrix, each as a 1-D +DenseMatrix. Throws an error if this Matrix is not 2-dimensional. + +#### .columns() + +Returns a JavaScript Array of the columns of this Matrix, each as a 1-D +DenseMatrix. Throws an error if this Matrix is not 2-dimensional. + +#### .toNumber() + +Returns this Matrix itself if the entries of the matrix are JavaScriot +`number` type. Otherwise (if possible) it returns a fresh Range consisting +of all of the entries of this Range converted to `number`. Throws an +error if such conversion is not possible. + +#### .toJSON() + +Returns a JSON (plain object) representation of this Range. This +representation can be restored to a Range using the static `.fromJson` +method. + +### New static methods + +#### DenseMatrix.fromJSON(json) + +Constructs a fresh Range from _json_, which should be the result of +a `.toJSON()` call on some Range. diff --git a/docs/reference/classes/sparsematrix.md b/docs/reference/classes/sparsematrix.md index 3007436180..74bfa9583b 100644 --- a/docs/reference/classes/sparsematrix.md +++ b/docs/reference/classes/sparsematrix.md @@ -1,245 +1,90 @@ ## SparseMatrix -Sparse Matrix implementation. This type implements a Compressed Column Storage format -for sparse matrices. - -* _instance_ - * [.storage()](#SparseMatrix+storage) ⇒ string - * [.datatype()](#SparseMatrix+datatype) ⇒ string - * [.create(data, [datatype])](#SparseMatrix+create) - * [.density()](#SparseMatrix+density) ⇒ number - * [.subset(index, [replacement], [defaultValue])](#SparseMatrix+subset) - * [.get(index)](#SparseMatrix+get) ⇒ \* - * [.set(index, value, [defaultValue])](#SparseMatrix+set) ⇒ [SparseMatrix](#SparseMatrix) - * [.resize(size, [defaultValue], [copy])](#SparseMatrix+resize) ⇒ Matrix - * [.clone()](#SparseMatrix+clone) ⇒ [SparseMatrix](#SparseMatrix) - * [.size()](#SparseMatrix+size) ⇒ Array.<number> - * [.map(callback, [skipZeros])](#SparseMatrix+map) ⇒ [SparseMatrix](#SparseMatrix) - * [.forEach(callback, [skipZeros])](#SparseMatrix+forEach) - * [.toArray()](#SparseMatrix+toArray) ⇒ Array - * [.valueOf()](#SparseMatrix+valueOf) ⇒ Array - * [.format([options])](#SparseMatrix+format) ⇒ string - * [.toString()](#SparseMatrix+toString) ⇒ string - * [.toJSON()](#SparseMatrix+toJSON) ⇒ Object - * [.diagonal([k])](#SparseMatrix+diagonal) ⇒ Matrix - * [.swapRows(i, j)](#SparseMatrix+swapRows) ⇒ Matrix -* _static_ - * [.fromJSON(json)](#SparseMatrix.fromJSON) ⇒ [SparseMatrix](#SparseMatrix) - * [.diagonal(size, value, [k], [datatype])](#SparseMatrix.diagonal) ⇒ [SparseMatrix](#SparseMatrix) - - -### sparseMatrix.storage() ⇒ string -Get the storage format used by the matrix. - -Usage: -```js -const format = matrix.storage() // retrieve storage format -``` - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: string - The storage format. - -### sparseMatrix.datatype() ⇒ string -Get the datatype of the data stored in the matrix. - -Usage: -```js -const format = matrix.datatype() // retrieve matrix datatype -``` - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: string - The datatype. - -### sparseMatrix.create(data, [datatype]) -Create a new SparseMatrix - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) - -| Param | Type | -| --- | --- | -| data | Array | -| [datatype] | string | - - -### sparseMatrix.density() ⇒ number -Get the matrix density. - -Usage: -```js -const density = matrix.density() // retrieve matrix density -``` - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: number - The matrix density. - -### sparseMatrix.subset(index, [replacement], [defaultValue]) -Get a subset of the matrix, or replace a subset of the matrix. - -Usage: -```js -const subset = matrix.subset(index) // retrieve subset -const value = matrix.subset(index, replacement) // replace subset -``` - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) - -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| index | Index | | | -| [replacement] | Array | Maytrix | \* | | | -| [defaultValue] | \* | 0 | Default value, filled in on new entries when the matrix is resized. If not provided, new matrix elements will be filled with zeros. | - - -### sparseMatrix.get(index) ⇒ \* -Get a single element from the matrix. - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: \* - value - -| Param | Type | Description | -| --- | --- | --- | -| index | Array.<number> | Zero-based index | - - -### sparseMatrix.set(index, value, [defaultValue]) ⇒ [SparseMatrix](#SparseMatrix) -Replace a single element in the matrix. - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: [SparseMatrix](#SparseMatrix) - self - -| Param | Type | Description | -| --- | --- | --- | -| index | Array.<number> | Zero-based index | -| value | \* | | -| [defaultValue] | \* | Default value, filled in on new entries when the matrix is resized. If not provided, new matrix elements will be set to zero. | - - -### sparseMatrix.resize(size, [defaultValue], [copy]) ⇒ Matrix -Resize the matrix to the given size. Returns a copy of the matrix when -`copy=true`, otherwise return the matrix itself (resize in place). - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: Matrix - The resized matrix - -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| size | Array.<number> | | The new size the matrix should have. | -| [defaultValue] | \* | 0 | Default value, filled in on new entries. If not provided, the matrix elements will be filled with zeros. | -| [copy] | boolean | | Return a resized copy of the matrix | - - -### sparseMatrix.clone() ⇒ [SparseMatrix](#SparseMatrix) -Create a clone of the matrix - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: [SparseMatrix](#SparseMatrix) - clone - -### sparseMatrix.size() ⇒ Array.<number> -Retrieve the size of the matrix. - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: Array.<number> - size - -### sparseMatrix.map(callback, [skipZeros]) ⇒ [SparseMatrix](#SparseMatrix) -Create a new matrix with the results of the callback function executed on -each entry of the matrix. - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: [SparseMatrix](#SparseMatrix) - matrix - -| Param | Type | Description | -| --- | --- | --- | -| callback | function | The callback function is invoked with three parameters: the value of the element, the index of the element, and the Matrix being traversed. | -| [skipZeros] | boolean | Invoke callback function for non-zero values only. | - - -### sparseMatrix.forEach(callback, [skipZeros]) -Execute a callback function on each entry of the matrix. - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) - -| Param | Type | Description | -| --- | --- | --- | -| callback | function | The callback function is invoked with three parameters: the value of the element, the index of the element, and the Matrix being traversed. | -| [skipZeros] | boolean | Invoke callback function for non-zero values only. | - - -### sparseMatrix.toArray() ⇒ Array -Create an Array with a copy of the data of the SparseMatrix - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: Array - array - -### sparseMatrix.valueOf() ⇒ Array -Get the primitive value of the SparseMatrix: a two dimensions array - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: Array - array - -### sparseMatrix.format([options]) ⇒ string -Get a string representation of the matrix, with optional formatting options. - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: string - str - -| Param | Type | Description | -| --- | --- | --- | -| [options] | Object | number | function | Formatting options. See lib/utils/number:format for a description of the available options. | - - -### sparseMatrix.toString() ⇒ string -Get a string representation of the matrix - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: string - str - -### sparseMatrix.toJSON() ⇒ Object -Get a JSON representation of the matrix - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) - -### sparseMatrix.diagonal([k]) ⇒ Matrix -Get the kth Matrix diagonal. - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: Matrix - The matrix vector with the diagonal values. - -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| [k] | number | BigNumber | 0 | The kth diagonal where the vector will retrieved. | - - -### sparseMatrix.swapRows(i, j) ⇒ Matrix -Swap rows i and j in Matrix. - -**Kind**: instance method of [SparseMatrix](#SparseMatrix) -**Returns**: Matrix - The matrix reference - -| Param | Type | Description | -| --- | --- | --- | -| i | number | Matrix row index 1 | -| j | number | Matrix row index 2 | - - -### SparseMatrix.fromJSON(json) ⇒ [SparseMatrix](#SparseMatrix) -Generate a matrix from a JSON object - -**Kind**: static method of [SparseMatrix](#SparseMatrix) - -| Param | Type | Description | -| --- | --- | --- | -| json | Object | An object structured like `{"mathjs": "SparseMatrix", "values": [], "index": [], "ptr": [], "size": []}`, where mathjs is optional | - - -### SparseMatrix.diagonal(size, value, [k], [datatype]) ⇒ [SparseMatrix](#SparseMatrix) -Create a diagonal matrix. - -**Kind**: static method of [SparseMatrix](#SparseMatrix) - -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| size | Array | | The matrix size. | -| value | number | Array | Matrix | | The values for the diagonal. | -| [k] | number | BigNumber | 0 | The kth diagonal where the vector will be filled in. | -| [datatype] | string | | The Matrix datatype, values must be of this datatype. | +This type implements a Compressed Column Storage format that provides greater +memory and speed efficiency for sparse matrices, i.e., matrices for which +a significant fraction of the entries are zero. (Note that when nearly all +entries are nonzero, this Storage format is less efficient than +[DenseMatrix](densematrix.md).) + +Note that currently the SparseMatrix implementation can only handle +2-dimensional matrices. Therefore, some methods of the [Matrix API](matrix.md) +may throw errors if they would entail the creation of a SparseMatrix of +dimensionality other than two. Such exceptions are not otherwise documented +here. + +This page documents all other differences and extensions to the basic +[Matrix](matrix.md) API. + +### Constructing a SparseMatrix + +#### new SparseMatrix(data, datatype?) + +The _data_ should be a (nested, rectangular) Array or a Matrix. Creates a +SparseMatrix whose contents consist of a fresh copy of the provided _data_. +Any supplied _datatype_ is trusted without checking. If it is not supplied, +the datatype is copied from the _data_ argument if it is a Matrix, and +otherwise it is simply left as `undefined`. If _data_ is unspecified, a +2-dimensional DenseMatrix with 0 extent in each dimension is constructed. + +### Instance methods of Matrix + +#### .storage() + +Always returns 'sparse' + +#### .datatype() + +May be undefined, even if the entries are of uniform type, if no datatype +was specified at construction time. See `.getDataType()` for determining +a datatype from the data of the matrix itself. + +### New instance methods provided by DenseMatrix + +#### .getDataType() + +Examines the entries of the matrix to see if they are of uniform type, and +if so, returns the string mathjs name of that type. If not, returns "mixed". + +#### .density() + +Returns the fraction of entries that are nonzero, as a JavaScript number. + +#### .diagonal(k?) + +When _k_ is not specified, returns the main diagonal of this Matrix, as +as a SparseMatrix. When it is, returns the diagonal _k_ entries above the +main diagonal when _k_ is positive, and the absolut value of _k_ entries +below the main diagonal when _k_ is negative. Note that these diagonals do +_not_ "wrap around", so in a an n×n matrix, `.diagonal(k)` will have n - |k| +entries. + +#### .toJSON() + +Returns a JSON (plain object) representation of this Matrix. This +representation can be restored to a SparseMatrix using the static `.fromJson` +method. + +#### swapRows(i, j) + +Alters this Matrix in place by swapping rows _i_ and _j_. + + +### New static methods + +#### DenseMatrix.diagonal(size, diagonalValue?, k?, offDiagValue?) + +Constructs a fresh diagonal SparseMatrix of the given _size_. The diagonal +entries are along the main diagonal if _k_ is not specified or zero; otherwise +they are along an offset diagonal as per the _k_ parameter of the +`.diagonal` instance method. The diagonal entries are set to _diagonalValue_ +if it is specified, or one otherwise. The off-diagonal entries are set to +_offDiagValue_ if it is specified, or zero otherwise. (Note that if both +the diagonal and off-diagonal values are nonzero, then SparseMatrix is likely +not a good storage format for the Matrix.) Thus `SparseMatrix.diagonal([3, 3])` +returns the usual 3×3 identity matrix. + +#### SparseMatrix.fromJSON(json) + +Constructs a fresh SparseMatrix from _json_, which should be the result of +a `.toJSON()` call on some SparseMatrix. diff --git a/package-lock.json b/package-lock.json index 4ccb555ac9..506cf8afc8 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": "^4.2.1" + "typed-function": "^4.2.2" }, "bin": { "mathjs": "bin/cli.js" @@ -117,6 +117,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2301,6 +2302,7 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -2365,6 +2367,7 @@ "integrity": "sha512-ixiWrCSRi33uqBMRuICcKECW7rtgY43TbsHDpM2XK7lXispd48opW+0IXrBVxv9NMhaz/Ue9kyj6r3NTVyXm8A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.20.0" } @@ -2422,6 +2425,7 @@ "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/types": "8.46.3", @@ -2836,6 +2840,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3820,6 +3825,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -5369,6 +5375,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -5454,6 +5461,7 @@ "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -5569,6 +5577,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5684,6 +5693,7 @@ "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "builtins": "^5.0.1", @@ -5797,6 +5807,7 @@ "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -8504,6 +8515,7 @@ "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@colors/colors": "1.5.0", "body-parser": "^1.19.0", @@ -10523,6 +10535,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11240,6 +11253,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -12595,9 +12609,9 @@ } }, "node_modules/typed-function": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.1.tgz", - "integrity": "sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.2.tgz", + "integrity": "sha512-VwaXim9Gp1bngi/q3do8hgttYn2uC3MoT/gfuMWylnj1IeZBUAyPddHZlo1K05BDoj8DYPpMdiHqH1dDYdJf2A==", "license": "MIT", "engines": { "node": ">= 18" @@ -12620,6 +12634,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13194,6 +13209,7 @@ "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", diff --git a/package.json b/package.json index 3d4c416e5d..383d8b7f1e 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": "^4.2.1" + "typed-function": "^4.2.2" }, "devDependencies": { "@babel/core": "7.28.5", diff --git a/src/core/function/typed.js b/src/core/function/typed.js index 8c527d29ad..895f241cca 100644 --- a/src/core/function/typed.js +++ b/src/core/function/typed.js @@ -132,10 +132,10 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi { name: 'string', test: isString }, { name: 'Chain', test: isChain }, { name: 'Array', test: isArray }, - { name: 'Matrix', test: isMatrix }, { name: 'DenseMatrix', test: isDenseMatrix }, { name: 'SparseMatrix', test: isSparseMatrix }, { name: 'Range', test: isRange }, + { name: 'Matrix', test: isMatrix }, { name: 'Index', test: isIndex }, { name: 'boolean', test: isBoolean }, { name: 'ResultSet', test: isResultSet }, @@ -167,6 +167,11 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi { name: 'Object', test: isObject } // order 'Object' last, it matches on other classes too ]) + // Note the order of conversions in this list plays an important + // role in selecting conversions. If there is no explicit signature for + // type T, but there are signatures for type A and type B and T is + // convertible to each of type A and type B, then the signature corresponding + // to the conversion that comes earlier on the below list will be preferred. typed.addConversions([ { from: 'number', @@ -235,12 +240,27 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi return new Fraction(x) } + }, { + from: 'number', + to: 'Fraction', + convert: function (x) { + if (!Fraction) { + throwNoFraction(x) + } + + const f = new Fraction(x) + if (f.valueOf() !== x) { + throw new TypeError('Cannot implicitly convert a number to a Fraction when there will be a loss of precision ' + + '(value: ' + x + '). ' + + 'Use function fraction(x) to convert to Fraction.') + } + return f + } }, { from: 'Fraction', - to: 'BigNumber', + to: 'number', convert: function (x) { - throw new TypeError('Cannot implicitly convert a Fraction to BigNumber or vice versa. ' + - 'Use function bignumber(x) to convert to BigNumber or fraction(x) to convert to Fraction.') + return x.valueOf() } }, { from: 'Fraction', @@ -253,29 +273,13 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi return new Complex(x.valueOf(), 0) } }, { - from: 'number', - to: 'Fraction', + from: 'Fraction', + to: 'BigNumber', convert: function (x) { - if (!Fraction) { - throwNoFraction(x) - } - - const f = new Fraction(x) - if (f.valueOf() !== x) { - throw new TypeError('Cannot implicitly convert a number to a Fraction when there will be a loss of precision ' + - '(value: ' + x + '). ' + - 'Use function fraction(x) to convert to Fraction.') - } - return f + throw new TypeError('Cannot implicitly convert a Fraction to BigNumber or vice versa. ' + + 'Use function bignumber(x) to convert to BigNumber or fraction(x) to convert to Fraction.') } }, { - // FIXME: add conversion from Fraction to number, for example for `sqrt(fraction(1,3))` - // from: 'Fraction', - // to: 'number', - // convert: function (x) { - // return x.valueOf() - // } - // }, { from: 'string', to: 'number', convert: function (x) { diff --git a/src/expression/embeddedDocs/embeddedDocs.js b/src/expression/embeddedDocs/embeddedDocs.js index 2e4383beb4..c6dc694687 100644 --- a/src/expression/embeddedDocs/embeddedDocs.js +++ b/src/expression/embeddedDocs/embeddedDocs.js @@ -55,6 +55,7 @@ import { ceilDocs } from './function/arithmetic/ceil.js' import { cubeDocs } from './function/arithmetic/cube.js' import { divideDocs } from './function/arithmetic/divide.js' import { dotDivideDocs } from './function/arithmetic/dotDivide.js' +import { scalarDivideDocs } from './function/arithmetic/scalarDivide.js' import { dotMultiplyDocs } from './function/arithmetic/dotMultiply.js' import { dotPowDocs } from './function/arithmetic/dotPow.js' import { expDocs } from './function/arithmetic/exp.js' @@ -75,11 +76,13 @@ import { multiplyDocs } from './function/arithmetic/multiply.js' import { normDocs } from './function/arithmetic/norm.js' import { nthRootDocs } from './function/arithmetic/nthRoot.js' import { nthRootsDocs } from './function/arithmetic/nthRoots.js' +import { oneDocs } from './function/arithmetic/one.js' import { powDocs } from './function/arithmetic/pow.js' import { roundDocs } from './function/arithmetic/round.js' import { signDocs } from './function/arithmetic/sign.js' import { sqrtDocs } from './function/arithmetic/sqrt.js' import { sqrtmDocs } from './function/arithmetic/sqrtm.js' +import { zeroDocs } from './function/arithmetic/zero.js' import { sylvesterDocs } from './function/algebra/sylvester.js' import { schurDocs } from './function/algebra/schur.js' import { lyapDocs } from './function/algebra/lyap.js' @@ -378,6 +381,7 @@ export const embeddedDocs = { cube: cubeDocs, divide: divideDocs, dotDivide: dotDivideDocs, + scalarDivide: scalarDivideDocs, dotMultiply: dotMultiplyDocs, dotPow: dotPowDocs, exp: expDocs, @@ -397,6 +401,7 @@ export const embeddedDocs = { norm: normDocs, nthRoot: nthRootDocs, nthRoots: nthRootsDocs, + one: oneDocs, pow: powDocs, round: roundDocs, sign: signDocs, @@ -408,6 +413,7 @@ export const embeddedDocs = { unaryPlus: unaryPlusDocs, xgcd: xgcdDocs, invmod: invmodDocs, + zero: zeroDocs, // functions - bitwise bitAnd: bitAndDocs, diff --git a/src/expression/embeddedDocs/function/arithmetic/one.js b/src/expression/embeddedDocs/function/arithmetic/one.js new file mode 100644 index 0000000000..9d53522198 --- /dev/null +++ b/src/expression/embeddedDocs/function/arithmetic/one.js @@ -0,0 +1,8 @@ +export const oneDocs = { + name: 'one', + category: 'arithmetic', + syntax: ['one(x)'], + description: 'returns the multiplicative identity of the same type as x', + examples: ['one(2/3)', 'one([[1, -1], [-1, 2]])'], + seealso: ['zero', 'typeOf', 'numeric'] +} diff --git a/src/expression/embeddedDocs/function/arithmetic/scalarDivide.js b/src/expression/embeddedDocs/function/arithmetic/scalarDivide.js new file mode 100644 index 0000000000..ba52ce87f1 --- /dev/null +++ b/src/expression/embeddedDocs/function/arithmetic/scalarDivide.js @@ -0,0 +1,17 @@ +export const scalarDivideDocs = { + name: 'scalarDivide', + category: 'arithmetic', + syntax: [ + 'scalarDivide(x, y)' + ], + description: 'Determine if one entity is a scalar multiple of another', + examples: [ + 'scalarDivide([3, 2, 0], [6, 4, 0])', + 'scalarDivide([3, 2, 1], [6, 4, 1])', + 'scalarDivide(3, 0)' + ], + seealso: [ + 'divide', + 'dotDivide' + ] +} diff --git a/src/expression/embeddedDocs/function/arithmetic/zero.js b/src/expression/embeddedDocs/function/arithmetic/zero.js new file mode 100644 index 0000000000..465fb4da27 --- /dev/null +++ b/src/expression/embeddedDocs/function/arithmetic/zero.js @@ -0,0 +1,8 @@ +export const zeroDocs = { + name: 'zero', + category: 'arithmetic', + syntax: ['zero(x)'], + description: 'returns the additive identity of the same type as x', + examples: ['zero(2/3)', 'zero([[1, -1, 1], [-1, 2, -1]])'], + seealso: ['one', 'typeOf', 'numeric'] +} diff --git a/src/expression/node/ArrayNode.js b/src/expression/node/ArrayNode.js index 145e96152f..36cad25cae 100644 --- a/src/expression/node/ArrayNode.js +++ b/src/expression/node/ArrayNode.js @@ -13,11 +13,15 @@ export const createArrayNode = /* #__PURE__ */ factory(name, dependencies, ({ No * @constructor ArrayNode * @extends {Node} * Holds an 1-dimensional array with items - * @param {Node[]} [items] 1 dimensional array with items + * @param {Node[]} [items] 1 dimensional array with items + * @param {boolean} [forceArray] + * Should the result always be Array regardless of config? (default + * is false) */ - constructor (items) { + constructor (items, forceArray = false) { super() this.items = items || [] + this.forceArray = forceArray // validate input if (!Array.isArray(this.items) || !this.items.every(isNode)) { @@ -47,7 +51,7 @@ export const createArrayNode = /* #__PURE__ */ factory(name, dependencies, ({ No return item._compile(math, argNames) }) - const asMatrix = (math.config.matrix !== 'Array') + const asMatrix = !this.forceArray && (math.config.matrix !== 'Array') if (asMatrix) { const matrix = math.matrix return function evalArrayNode (scope, args, context) { diff --git a/src/expression/parse.js b/src/expression/parse.js index 7be4f883ee..e04e6d2fa4 100644 --- a/src/expression/parse.js +++ b/src/expression/parse.js @@ -1745,28 +1745,37 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ * @private */ function parseParentheses (state) { - let node - // check if it is a parenthesized expression - if (state.token === '(') { - // parentheses (...) - openParams(state) - getToken(state) - - node = parseAssignment(state) // start again + if (state.token !== '(') return parseEnd(state) + // Yes, we have parentheses (...) + openParams(state) + getToken(state) - if (state.token !== ')') { - throw createSyntaxError(state, 'Parenthesis ) expected') - } + if (state.token === ')') { // `()` is empty array closeParams(state) getToken(state) + return parseAccessors(state, new ArrayNode([], true)) + } - node = new ParenthesisNode(node) - node = parseAccessors(state, node) - return node + let node = parseAssignment(state) // start again + + if (state.token === ',') { // Array notation + const items = [node] + do { + getToken(state) + if (state.token === ')') break + items.push(parseAssignment(state)) + } while (state.token === ',') + node = new ArrayNode(items, true) + } else node = new ParenthesisNode(node) + + if (state.token !== ')') { + throw createSyntaxError(state, 'Parenthesis ) expected') } + closeParams(state) + getToken(state) - return parseEnd(state) + return parseAccessors(state, node) } /** diff --git a/src/expression/transform/and.transform.js b/src/expression/transform/and.transform.js index 4212eecefe..6440b3f390 100644 --- a/src/expression/transform/and.transform.js +++ b/src/expression/transform/and.transform.js @@ -3,10 +3,10 @@ import { factory } from '../../utils/factory.js' import { isCollection } from '../../utils/is.js' const name = 'and' -const dependencies = ['typed', 'matrix', 'zeros', 'add', 'equalScalar', 'not', 'concat'] +const dependencies = ['typed', 'DenseMatrix', 'zeros', 'add', 'equalScalar', 'not'] -export const createAndTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, zeros, not, concat }) => { - const and = createAnd({ typed, matrix, equalScalar, zeros, not, concat }) +export const createAndTransform = /* #__PURE__ */ factory(name, dependencies, provided => { + const and = createAnd(provided) function andTransform (args, math, scope) { const condition1 = args[0].compile().evaluate(scope) diff --git a/src/expression/transform/bitAnd.transform.js b/src/expression/transform/bitAnd.transform.js index ff9e709261..b3b80d8627 100644 --- a/src/expression/transform/bitAnd.transform.js +++ b/src/expression/transform/bitAnd.transform.js @@ -3,10 +3,10 @@ import { factory } from '../../utils/factory.js' import { isCollection } from '../../utils/is.js' const name = 'bitAnd' -const dependencies = ['typed', 'matrix', 'zeros', 'add', 'equalScalar', 'not', 'concat'] +const dependencies = ['typed', 'DenseMatrix', 'equalScalar'] -export const createBitAndTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, zeros, not, concat }) => { - const bitAnd = createBitAnd({ typed, matrix, equalScalar, zeros, not, concat }) +export const createBitAndTransform = /* #__PURE__ */ factory(name, dependencies, provided => { + const bitAnd = createBitAnd(provided) function bitAndTransform (args, math, scope) { const condition1 = args[0].compile().evaluate(scope) diff --git a/src/expression/transform/bitOr.transform.js b/src/expression/transform/bitOr.transform.js index ae04f7de04..d337422cc7 100644 --- a/src/expression/transform/bitOr.transform.js +++ b/src/expression/transform/bitOr.transform.js @@ -3,10 +3,10 @@ import { factory } from '../../utils/factory.js' import { isCollection } from '../../utils/is.js' const name = 'bitOr' -const dependencies = ['typed', 'matrix', 'equalScalar', 'DenseMatrix', 'concat'] +const dependencies = ['typed', 'equalScalar', 'DenseMatrix'] -export const createBitOrTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, DenseMatrix, concat }) => { - const bitOr = createBitOr({ typed, matrix, equalScalar, DenseMatrix, concat }) +export const createBitOrTransform = /* #__PURE__ */ factory(name, dependencies, provided => { + const bitOr = createBitOr(provided) function bitOrTransform (args, math, scope) { const condition1 = args[0].compile().evaluate(scope) diff --git a/src/expression/transform/index.transform.js b/src/expression/transform/index.transform.js index 40ffe3694a..dda06ca918 100644 --- a/src/expression/transform/index.transform.js +++ b/src/expression/transform/index.transform.js @@ -4,9 +4,9 @@ import { import { factory } from '../../utils/factory.js' const name = 'index' -const dependencies = ['Index', 'getMatrixDataType'] +export const dependencies = ['Index', 'Range', 'number', 'getMatrixDataType'] -export const createIndexTransform = /* #__PURE__ */ factory(name, dependencies, ({ Index, getMatrixDataType }) => { +export const createIndexTransform = /* #__PURE__ */ factory(name, dependencies, ({ Index, Range, number, getMatrixDataType }) => { /** * Attach a transform function to math.index * Adds a property transform containing the transform function. @@ -20,8 +20,11 @@ export const createIndexTransform = /* #__PURE__ */ factory(name, dependencies, // change from one-based to zero based, convert BigNumber to number and leave Array of Booleans as is if (isRange(arg)) { - arg.start-- - arg.end -= (arg.step > 0 ? 0 : 2) + arg = new Range({ + start: number(arg.start) - 1, + length: arg.length, + step: number(arg.step) + }) } else if (arg && arg.isSet === true) { arg = arg.map(function (v) { return v - 1 }) } else if (isArray(arg) || isMatrix(arg)) { @@ -33,7 +36,7 @@ export const createIndexTransform = /* #__PURE__ */ factory(name, dependencies, } else if (isBigNumber(arg)) { arg = arg.toNumber() - 1 } else if (typeof arg === 'string') { - // leave as is + // leave as is, will be interpreted later } else { throw new TypeError('Dimension must be an Array, Matrix, number, bigint, string, or Range') } @@ -43,6 +46,8 @@ export const createIndexTransform = /* #__PURE__ */ factory(name, dependencies, const res = new Index() Index.apply(res, args) + res.includeEnd = true + res.shiftPosition = 1 return res } }, { isTransformFunction: true }) diff --git a/src/expression/transform/nullish.transform.js b/src/expression/transform/nullish.transform.js index 7c6e733be6..26e7823726 100644 --- a/src/expression/transform/nullish.transform.js +++ b/src/expression/transform/nullish.transform.js @@ -3,10 +3,10 @@ import { factory } from '../../utils/factory.js' import { isCollection } from '../../utils/is.js' const name = 'nullish' -const dependencies = ['typed', 'matrix', 'size', 'flatten', 'deepEqual'] +const dependencies = ['typed', 'DenseMatrix', 'size', 'flatten', 'deepEqual'] -export const createNullishTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, size, flatten, deepEqual }) => { - const nullish = createNullish({ typed, matrix, size, flatten, deepEqual }) +export const createNullishTransform = /* #__PURE__ */ factory(name, dependencies, provided => { + const nullish = createNullish(provided) function nullishTransform (args, math, scope) { const left = args[0].compile().evaluate(scope) diff --git a/src/expression/transform/range.transform.js b/src/expression/transform/range.transform.js index 67211d4b94..1b6d876a2e 100644 --- a/src/expression/transform/range.transform.js +++ b/src/expression/transform/range.transform.js @@ -1,11 +1,10 @@ import { factory } from '../../utils/factory.js' -import { createRange } from '../../function/matrix/range.js' +import { createRange, dependencies } from '../../function/matrix/range.js' const name = 'range' -const dependencies = ['typed', 'config', '?matrix', '?bignumber', 'equal', 'smaller', 'smallerEq', 'larger', 'largerEq', 'add', 'isZero', 'isPositive'] -export const createRangeTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, matrix, bignumber, equal, smaller, smallerEq, larger, largerEq, add, isZero, isPositive }) => { - const range = createRange({ typed, config, matrix, bignumber, equal, smaller, smallerEq, larger, largerEq, add, isZero, isPositive }) +export const createRangeTransform = /* #__PURE__ */ factory(name, dependencies, provided => { + const range = createRange(provided) /** * Attach a transform function to math.range @@ -13,15 +12,22 @@ export const createRangeTransform = /* #__PURE__ */ factory(name, dependencies, * * This transform creates a range which includes the end value */ - return typed('range', { + return provided.typed('range', { '...any': function (args) { const lastIndex = args.length - 1 + if (lastIndex < 0) { + throw new SyntaxError('range() requires at least one argument') + } const last = args[lastIndex] if (typeof last !== 'boolean') { // append a parameter includeEnd=true args.push(true) } - + const first = args[0] + if (typeof first === 'string' && first.charAt(0) === ':') { + // default start in expressions is 1 + args[0] = '1' + first + } return range.apply(null, args) } }) diff --git a/src/expression/transform/subset.transform.js b/src/expression/transform/subset.transform.js index 50e1f41755..6b6393a6b9 100644 --- a/src/expression/transform/subset.transform.js +++ b/src/expression/transform/subset.transform.js @@ -1,26 +1,44 @@ import { factory } from '../../utils/factory.js' import { errorTransform } from './utils/errorTransform.js' -import { createSubset } from '../../function/matrix/subset.js' +import { + createSubset, dependencies as subsetDependencies +} from '../../function/matrix/subset.js' +import { + createIndexTransform, dependencies as indexTransformDependencies +} from './index.transform.js' const name = 'subset' -const dependencies = ['typed', 'matrix', 'zeros', 'add'] +const dependencies = subsetDependencies.slice() +for (const indexDep of indexTransformDependencies) { + if (!dependencies.includes(indexDep)) dependencies.push(indexDep) +} -export const createSubsetTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, zeros, add }) => { - const subset = createSubset({ typed, matrix, zeros, add }) +export const createSubsetTransform = /* #__PURE__ */ factory( + name, + dependencies, + provided => { + const subset = createSubset(provided) + const indexTransform = createIndexTransform(provided) - /** - * Attach a transform function to math.subset - * Adds a property transform containing the transform function. - * - * This transform creates a range which includes the end value - */ - return typed('subset', { - '...any': function (args) { - try { - return subset.apply(null, args) - } catch (err) { - throw errorTransform(err) + /** + * Attach a transform function to math.subset + * Adds a property transform containing the transform function. + * + * This transform creates a range which includes the end value + */ + return provided.typed('subset', { + '...any': function (args) { + try { + if (args[1] && Array.isArray(args[1])) { + // supplied an array instead of an index for the 2nd argument, so + // have to turn that into an Index with indexTransform rather than + // just plain index, as subset will if we just let it have at it. + args[1] = indexTransform.apply(null, args[1]) + } + return subset.apply(null, args) + } catch (err) { + throw errorTransform(err) + } } - } - }) -}, { isTransformFunction: true }) + }) + }, { isTransformFunction: true }) diff --git a/src/factoriesAny.js b/src/factoriesAny.js index 15ea5de0d0..921412b30d 100644 --- a/src/factoriesAny.js +++ b/src/factoriesAny.js @@ -114,6 +114,7 @@ export { createToBest } from './function/unit/toBest.js' export { createIsPrime } from './function/utils/isPrime.js' export { createNumeric } from './function/utils/numeric.js' export { createDivideScalar } from './function/arithmetic/divideScalar.js' +export { createOneUnitless, createOne } from './function/arithmetic/one.js' export { createPow } from './function/arithmetic/pow.js' export { createRound } from './function/arithmetic/round.js' export { createLog } from './function/arithmetic/log.js' @@ -121,6 +122,8 @@ export { createLog1p } from './function/arithmetic/log1p.js' export { createNthRoots } from './function/arithmetic/nthRoots.js' export { createDotPow } from './function/arithmetic/dotPow.js' export { createDotDivide } from './function/arithmetic/dotDivide.js' +export { createScalarDivide } from './function/arithmetic/scalarDivide.js' +export { createZero } from './function/arithmetic/zero.js' export { createLsolve } from './function/algebra/solver/lsolve.js' export { createUsolve } from './function/algebra/solver/usolve.js' export { createLsolveAll } from './function/algebra/solver/lsolveAll.js' diff --git a/src/factoriesNumber.js b/src/factoriesNumber.js index 73a0eaafb5..4ead512191 100644 --- a/src/factoriesNumber.js +++ b/src/factoriesNumber.js @@ -83,6 +83,7 @@ export { createTyped } from './core/function/typed.js' // classes export { createResultSet } from './type/resultset/ResultSet.js' export { createRangeClass } from './type/matrix/Range.js' +export { createMatrixClass } from './type/matrix/Matrix.js' export { createHelpClass } from './expression/Help.js' export { createChainClass } from './type/chain/Chain.js' export { createHelp } from './expression/function/help.js' @@ -105,6 +106,7 @@ export const createSubtractScalar = /* #__PURE__ */ createNumberFactory('subtrac export const createCbrt = /* #__PURE__ */ createNumberFactory('cbrt', cbrtNumber) export { createCeilNumber as createCeil } from './function/arithmetic/ceil.js' export const createCube = /* #__PURE__ */ createNumberFactory('cube', cubeNumber) +export { createDotMultiplyNumber } from './function/arithmetic/dotMultiply.js' export const createExp = /* #__PURE__ */ createNumberFactory('exp', expNumber) export const createExpm1 = /* #__PURE__ */ createNumberFactory('expm1', expm1Number) export { createFixNumber as createFix } from './function/arithmetic/fix.js' @@ -115,7 +117,7 @@ export const createLog10 = /* #__PURE__ */ createNumberFactory('log10', log10Num export const createLog2 = /* #__PURE__ */ createNumberFactory('log2', log2Number) export const createMod = /* #__PURE__ */ createNumberFactory('mod', modNumber) export const createMultiplyScalar = /* #__PURE__ */ createNumberFactory('multiplyScalar', multiplyNumber) -export const createMultiply = /* #__PURE__ */ createNumberFactory('multiply', multiplyNumber) +export { createMultiplyNumber } from './function/arithmetic/multiply.js' export const createNthRoot = /* #__PURE__ */ createNumberOptionalSecondArgFactory('nthRoot', nthRootNumber) export const createSign = /* #__PURE__ */ createNumberFactory('sign', signNumber) @@ -125,8 +127,10 @@ export const createSubtract = /* #__PURE__ */ createNumberFactory('subtract', su export const createXgcd = /* #__PURE__ */ createNumberFactory('xgcd', xgcdNumber) export const createDivideScalar = /* #__PURE__ */ createNumberFactory('divideScalar', divideNumber) export const createPow = /* #__PURE__ */ createNumberFactory('pow', powNumber) +export { createOneNumber } from './function/arithmetic/one.js' export const createRound = /* #__PURE__ */ createNumberOptionalSecondArgFactory('round', roundNumber) +export { createScalarDivide } from './function/arithmetic/scalarDivide.js' export const createLog = /* #__PURE__ */ createNumberOptionalSecondArgFactory('log', logNumber) export const createLog1p = /* #__PURE__ */ createNumberFactory('log1p', log1pNumber) @@ -134,6 +138,7 @@ export const createAdd = /* #__PURE__ */ createNumberFactory('add', addNumber) export { createHypot } from './function/arithmetic/hypot.js' export const createNorm = /* #__PURE__ */ createNumberFactory('norm', normNumber) export const createDivide = /* #__PURE__ */ createNumberFactory('divide', divideNumber) +export { createZeroNumber } from './function/arithmetic/zero.js' // bitwise export const createBitAnd = /* #__PURE__ */ createNumberFactory('bitAnd', bitAndNumber) @@ -209,6 +214,7 @@ export const createOr = /* #__PURE__ */ createNumberFactory('or', orNumber) export const createXor = /* #__PURE__ */ createNumberFactory('xor', xorNumber) // matrix +export { createGetMatrixDataType } from './function/matrix/getMatrixDataType.js' export { createMapSlices } from './function/matrix/mapSlices.js' export { createFilter } from './function/matrix/filter.js' export { createForEach } from './function/matrix/forEach.js' @@ -219,6 +225,8 @@ export { createSize } from './function/matrix/size.js' export const createIndex = /* #__PURE__ */ factory('index', [], () => noIndex) export const createMatrix = /* #__PURE__ */ factory('matrix', [], () => noMatrix) // FIXME: needed now because subset transform needs it. Remove the need for it in subset export const createSubset = /* #__PURE__ */ factory('subset', [], () => noSubset) +export { createSqueeze } from './function/matrix/squeeze.js' + // TODO: provide number+array implementations for map, filter, forEach, zeros, ...? // TODO: create range implementation for range? export { createPartitionSelect } from './function/matrix/partitionSelect.js' diff --git a/src/function/arithmetic/add.js b/src/function/arithmetic/add.js index b9bc0ae9af..0402aebe64 100644 --- a/src/function/arithmetic/add.js +++ b/src/function/arithmetic/add.js @@ -7,22 +7,20 @@ import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgori const name = 'add' const dependencies = [ 'typed', - 'matrix', 'addScalar', 'equalScalar', 'DenseMatrix', - 'SparseMatrix', - 'concat' + 'SparseMatrix' ] export const createAdd = /* #__PURE__ */ factory( name, dependencies, - ({ typed, matrix, addScalar, equalScalar, DenseMatrix, SparseMatrix, concat }) => { + ({ typed, addScalar, equalScalar, DenseMatrix, SparseMatrix, math, concat }) => { const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) const matAlgo04xSidSid = createMatAlgo04xSidSid({ typed, equalScalar }) const matAlgo10xSids = createMatAlgo10xSids({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Add two or more values, `x + y`. * For matrices, the function is evaluated element wise. @@ -70,7 +68,29 @@ export const createAdd = /* #__PURE__ */ factory( } return result - }) + }), + 'Range, Range': typed.referToSelf(self => (r, p) => { + if (r.for !== p.for) throw new Error('Range length mismatch') + return r.createRange({ + start: self(r.start, p.start), + length: r.length, + step: self(r.step, p.step) + }) + }), + 'Range, Matrix': typed.referToSelf( + self => (r, m) => self(r.valueOf(), m)), + 'Range, any': typed.referToSelf(self => (r, s) => r.createRange({ + start: self(r.start, s), + length: r.length, + step: r.step + })), + 'Matrix, Range': typed.referToSelf( + self => (m, r) => self(m, r.valueOf())), + 'any, Range': typed.referToSelf(self => (s, r) => r.createRange({ + start: self(s, r.start), + length: r.length, + step: r.step + })) }, matrixAlgorithmSuite({ elop: addScalar, diff --git a/src/function/arithmetic/ceil.js b/src/function/arithmetic/ceil.js index a3f40bbbed..d1480769c3 100644 --- a/src/function/arithmetic/ceil.js +++ b/src/function/arithmetic/ceil.js @@ -8,7 +8,9 @@ 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'] +const dependencies = [ + 'typed', 'config', 'round', 'equalScalar', 'isZero', 'DenseMatrix', 'sparse' +] const bigTen = new Decimal(10) @@ -46,7 +48,9 @@ export const createCeilNumber = /* #__PURE__ */ factory( } ) -export const createCeil = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, round, matrix, equalScalar, zeros, DenseMatrix }) => { +export const createCeil = /* #__PURE__ */ factory(name, dependencies, ({ + typed, config, round, equalScalar, isZero, DenseMatrix, sparse +}) => { const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) const matAlgo14xDs = createMatAlgo14xDs({ typed }) @@ -181,16 +185,18 @@ export const createCeil = /* #__PURE__ */ factory(name, dependencies, ({ typed, 'number | Complex | Fraction | BigNumber, Array': typed.referToSelf(self => (x, y) => { // use matrix implementation - return matAlgo14xDs(matrix(y), x, self, true).valueOf() + return matAlgo14xDs(new DenseMatrix(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) - }) + 'number | Complex | BigNumber | Fraction, SparseMatrix': typed.referToSelf( + self => (x, n) => { + const dense = matAlgo12xSfs(n, x, self, true) + if (isZero(x)) return sparse(dense) + return dense + } + ), + + 'number | Complex | BigNumber | Fraction, DenseMatrix': typed.referToSelf( + self => (x, n) => matAlgo14xDs(n, x, self, true)) }) }) diff --git a/src/function/arithmetic/divide.js b/src/function/arithmetic/divide.js index 84840b7f0f..db8ebe9308 100644 --- a/src/function/arithmetic/divide.js +++ b/src/function/arithmetic/divide.js @@ -6,14 +6,14 @@ import { createMatAlgo14xDs } from '../../type/matrix/utils/matAlgo14xDs.js' const name = 'divide' const dependencies = [ 'typed', - 'matrix', + 'DenseMatrix', 'multiply', 'equalScalar', 'divideScalar', 'inv' ] -export const createDivide = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, multiply, equalScalar, divideScalar, inv }) => { +export const createDivide = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix, multiply, equalScalar, divideScalar, inv }) => { const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) const matAlgo14xDs = createMatAlgo14xDs({ typed }) @@ -69,9 +69,15 @@ export const createDivide = /* #__PURE__ */ factory(name, dependencies, ({ typed 'Array, any': function (x, y) { // use matrix implementation - return matAlgo14xDs(matrix(x), y, divideScalar, false).valueOf() + return matAlgo14xDs(new DenseMatrix(x), y, divideScalar, false).valueOf() }, + 'Range, any': typed.referToSelf(self => (r, s) => r.createRange({ + start: self(r.start, s), + length: r.length, + step: self(r.step, s) + })), + 'any, Array | Matrix': function (x, y) { return multiply(x, inv(y)) } diff --git a/src/function/arithmetic/dotDivide.js b/src/function/arithmetic/dotDivide.js index 3587faf762..db68306fa8 100644 --- a/src/function/arithmetic/dotDivide.js +++ b/src/function/arithmetic/dotDivide.js @@ -9,11 +9,9 @@ import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgori const name = 'dotDivide' const dependencies = [ 'typed', - 'matrix', 'equalScalar', 'divideScalar', 'DenseMatrix', - 'concat', 'SparseMatrix' ] @@ -23,7 +21,7 @@ export const createDotDivide = /* #__PURE__ */ factory(name, dependencies, ({ ty const matAlgo07xSSf = createMatAlgo07xSSf({ typed, SparseMatrix }) const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Divide two matrices element wise. The function accepts both matrices and diff --git a/src/function/arithmetic/dotMultiply.js b/src/function/arithmetic/dotMultiply.js index d49287bc6b..6cb87cff28 100644 --- a/src/function/arithmetic/dotMultiply.js +++ b/src/function/arithmetic/dotMultiply.js @@ -1,4 +1,6 @@ import { factory } from '../../utils/factory.js' +import { isArray } from '../../utils/is.js' +import { deepMultiply } from '../../plain/number/arithmetic.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' @@ -7,17 +9,16 @@ import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgori const name = 'dotMultiply' const dependencies = [ 'typed', - 'matrix', + 'DenseMatrix', 'equalScalar', - 'multiplyScalar', - 'concat' + 'multiplyScalar' ] -export const createDotMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, multiplyScalar, concat }) => { +export const createDotMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix, equalScalar, multiplyScalar }) => { const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) const matAlgo09xS0Sf = createMatAlgo09xS0Sf({ typed, equalScalar }) const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Multiply two matrices element wise. The function accepts both matrices and @@ -52,3 +53,30 @@ export const createDotMultiply = /* #__PURE__ */ factory(name, dependencies, ({ Ss: matAlgo11xS0s })) }) + +export const createDotMultiplyNumber = /* #__PURE__ */ factory( + name, ['typed'], ({ typed }) => { + return typed(name, { + 'number, number': (m, n) => m * n, + 'number, Array': deepMultiply, + 'Array, number': (A, n) => deepMultiply(n, A), + 'Array, Array': _dotMult + }) + } +) + +/* Multiply corresponding entries of A and B */ +function _dotMult (A, B) { + if (A.length !== B.length) { + throw new Error('Cannot dot-multiply arrays of differing length.') + } + return A.map((a, ix) => { + const b = B[ix] + if (isArray(a)) { + if (isArray(b)) return _dotMult(a, b) + } else { + if (!isArray(b)) return a * b + } + throw new Error('Cannot dot-multiply arrays of different shape.') + }) +} diff --git a/src/function/arithmetic/dotPow.js b/src/function/arithmetic/dotPow.js index a1c25f1522..b4188ed436 100644 --- a/src/function/arithmetic/dotPow.js +++ b/src/function/arithmetic/dotPow.js @@ -9,19 +9,17 @@ const name = 'dotPow' const dependencies = [ 'typed', 'equalScalar', - 'matrix', 'pow', 'DenseMatrix', - 'concat', 'SparseMatrix' ] -export const createDotPow = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar, matrix, pow, DenseMatrix, concat, SparseMatrix }) => { +export const createDotPow = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar, pow, DenseMatrix, SparseMatrix }) => { const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) const matAlgo07xSSf = createMatAlgo07xSSf({ typed, SparseMatrix }) const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) const powScalarSignatures = {} for (const signature in pow.signatures) { diff --git a/src/function/arithmetic/fix.js b/src/function/arithmetic/fix.js index 0cc47ffc0f..8dc6dce1f6 100644 --- a/src/function/arithmetic/fix.js +++ b/src/function/arithmetic/fix.js @@ -4,7 +4,9 @@ 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'] +const dependencies = [ + 'typed', 'Complex', 'ceil', 'floor', 'isZero', 'DenseMatrix', 'sparse' +] export const createFixNumber = /* #__PURE__ */ factory( name, ['typed', 'ceil', 'floor'], ({ typed, ceil, floor }) => { @@ -20,7 +22,9 @@ export const createFixNumber = /* #__PURE__ */ factory( } ) -export const createFix = /* #__PURE__ */ factory(name, dependencies, ({ typed, Complex, matrix, ceil, floor, equalScalar, zeros, DenseMatrix }) => { +export const createFix = /* #__PURE__ */ factory(name, dependencies, ({ + typed, Complex, matrix, ceil, floor, isZero, DenseMatrix, sparse +}) => { const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) const matAlgo14xDs = createMatAlgo14xDs({ typed }) @@ -143,16 +147,18 @@ export const createFix = /* #__PURE__ */ factory(name, dependencies, ({ typed, C 'number | Complex | Fraction | BigNumber, Array': typed.referToSelf(self => (x, y) => { // use matrix implementation - return matAlgo14xDs(matrix(y), x, self, true).valueOf() + return matAlgo14xDs(new DenseMatrix(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) - }) + 'number | Complex | BigNumber | Fraction, SparseMatrix': typed.referToSelf( + self => (x, n) => { + const dense = matAlgo12xSfs(n, x, self, true) + if (isZero(x)) return sparse(dense) + return dense + } + ), + + 'number | Complex | BigNumber | Fraction, DenseMatrix': typed.referToSelf( + self => (x, n) => matAlgo14xDs(n, x, self, true)) }) }) diff --git a/src/function/arithmetic/floor.js b/src/function/arithmetic/floor.js index 752d06864d..172ec9bed3 100644 --- a/src/function/arithmetic/floor.js +++ b/src/function/arithmetic/floor.js @@ -8,7 +8,9 @@ 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'] +const dependencies = [ + 'typed', 'config', 'round', 'equalScalar', 'isZero', 'DenseMatrix', 'sparse' +] const bigTen = new Decimal(10) @@ -53,7 +55,9 @@ export const createFloorNumber = /* #__PURE__ */ factory( } ) -export const createFloor = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, round, matrix, equalScalar, zeros, DenseMatrix }) => { +export const createFloor = /* #__PURE__ */ factory(name, dependencies, ({ + typed, config, round, equalScalar, isZero, DenseMatrix, sparse +}) => { const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) const matAlgo14xDs = createMatAlgo14xDs({ typed }) @@ -191,16 +195,18 @@ export const createFloor = /* #__PURE__ */ factory(name, dependencies, ({ typed, 'number | Complex | Fraction | BigNumber, Array': typed.referToSelf(self => (x, y) => { // use matrix implementation - return matAlgo14xDs(matrix(y), x, self, true).valueOf() + return matAlgo14xDs(new DenseMatrix(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) - }) + 'number | Complex | BigNumber | Fraction, SparseMatrix': typed.referToSelf( + self => (x, n) => { + const dense = matAlgo12xSfs(n, x, self, true) + if (isZero(x)) return sparse(dense) + return dense + } + ), + + 'number | Complex | BigNumber | Fraction, DenseMatrix': typed.referToSelf( + self => (x, n) => matAlgo14xDs(n, x, self, true)) }) }) diff --git a/src/function/arithmetic/gcd.js b/src/function/arithmetic/gcd.js index d370c498d6..e0f94c0f3f 100644 --- a/src/function/arithmetic/gcd.js +++ b/src/function/arithmetic/gcd.js @@ -12,12 +12,11 @@ const dependencies = [ 'typed', 'config', 'round', - 'matrix', 'equalScalar', - 'zeros', + 'isZero', 'BigNumber', 'DenseMatrix', - 'concat' + 'sparse' ] const gcdTypes = 'number | BigNumber | Fraction | Matrix | Array' @@ -27,12 +26,16 @@ function is1d (array) { return !array.some(element => Array.isArray(element)) } -export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, config, round, equalScalar, zeros, BigNumber, DenseMatrix, concat }) => { - const mod = createMod({ typed, config, round, matrix, equalScalar, zeros, DenseMatrix, concat }) +export const createGcd = /* #__PURE__ */ factory(name, dependencies, ({ + typed, config, round, equalScalar, isZero, BigNumber, DenseMatrix, sparse +}) => { + const mod = createMod({ + typed, config, round, equalScalar, isZero, DenseMatrix, sparse + }) const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) const matAlgo04xSidSid = createMatAlgo04xSidSid({ typed, equalScalar }) const matAlgo10xSids = createMatAlgo10xSids({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Calculate the greatest common divisor for two or more values or arrays. diff --git a/src/function/arithmetic/lcm.js b/src/function/arithmetic/lcm.js index d53dd3e799..0e66503180 100644 --- a/src/function/arithmetic/lcm.js +++ b/src/function/arithmetic/lcm.js @@ -8,16 +8,15 @@ import { lcmNumber } from '../../plain/number/index.js' const name = 'lcm' const dependencies = [ 'typed', - 'matrix', - 'equalScalar', - 'concat' + 'DenseMatrix', + 'equalScalar' ] -export const createLcm = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, concat }) => { +export const createLcm = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix, equalScalar, concat }) => { const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) const matAlgo06xS0S0 = createMatAlgo06xS0S0({ typed, equalScalar }) const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) const lcmTypes = 'number | BigNumber | Fraction | Matrix | Array' const lcmManySignature = {} diff --git a/src/function/arithmetic/mod.js b/src/function/arithmetic/mod.js index dee0b97e4a..1192a53049 100644 --- a/src/function/arithmetic/mod.js +++ b/src/function/arithmetic/mod.js @@ -12,21 +12,24 @@ const dependencies = [ 'typed', 'config', 'round', - 'matrix', 'equalScalar', - 'zeros', + 'isZero', 'DenseMatrix', - 'concat' + 'sparse' ] -export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, round, matrix, equalScalar, zeros, DenseMatrix, concat }) => { - const floor = createFloor({ typed, config, round, matrix, equalScalar, zeros, DenseMatrix }) +export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ + typed, config, round, equalScalar, isZero, DenseMatrix, sparse +}) => { + const floor = createFloor({ + typed, config, round, equalScalar, isZero, DenseMatrix, sparse + }) 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, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Calculates the modulus, the remainder of an integer division. diff --git a/src/function/arithmetic/multiply.js b/src/function/arithmetic/multiply.js index 07e1553b83..e8c8845c9c 100644 --- a/src/function/arithmetic/multiply.js +++ b/src/function/arithmetic/multiply.js @@ -1,20 +1,21 @@ import { factory } from '../../utils/factory.js' import { isMatrix } from '../../utils/is.js' import { arraySize } from '../../utils/array.js' +import { deepMultiply } from '../../plain/number/arithmetic.js' import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js' import { createMatAlgo14xDs } from '../../type/matrix/utils/matAlgo14xDs.js' const name = 'multiply' const dependencies = [ 'typed', - 'matrix', + 'DenseMatrix', 'addScalar', 'multiplyScalar', 'equalScalar', 'dot' ] -export const createMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, addScalar, multiplyScalar, equalScalar, dot }) => { +export const createMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix, addScalar, multiplyScalar, equalScalar, dot }) => { const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) const matAlgo14xDs = createMatAlgo14xDs({ typed }) @@ -801,7 +802,7 @@ export const createMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typ _validateMatrixDimensions(arraySize(x), arraySize(y)) // use dense matrix implementation - const m = selfMM(matrix(x), matrix(y)) + const m = selfMM(new DenseMatrix(x), new DenseMatrix(y)) // return array or scalar return isMatrix(m) ? m.valueOf() : m }), @@ -834,11 +835,11 @@ export const createMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typ }, 'Matrix, Array': typed.referTo('Matrix,Matrix', selfMM => - (x, y) => selfMM(x, matrix(y))), + (x, y) => selfMM(x, x.create(y))), 'Array, Matrix': typed.referToSelf(self => (x, y) => { // use Matrix * Matrix implementation - return self(matrix(x, y.storage()), y) + return self(y.create(x), y) }), 'SparseMatrix, any': function (x, y) { @@ -859,14 +860,26 @@ export const createMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typ 'Array, any': function (x, y) { // use matrix implementation - return matAlgo14xDs(matrix(x), y, multiplyScalar, false).valueOf() + return matAlgo14xDs(new DenseMatrix(x), y, multiplyScalar, false).valueOf() }, 'any, Array': function (x, y) { // use matrix implementation - return matAlgo14xDs(matrix(y), x, multiplyScalar, true).valueOf() + return matAlgo14xDs(new DenseMatrix(y), x, multiplyScalar, true).valueOf() }, + 'Range, any': typed.referToSelf(self => (r, s) => r.createRange({ + start: self(r.start, s), + length: r.length, + step: self(r.step, s) + })), + + 'any, Range': typed.referToSelf(self => (s, r) => r.createRange({ + start: self(s, r.start), + length: r.length, + step: self(s, r.step) + })), + 'any, any': multiplyScalar, 'any, any, ...any': typed.referToSelf(self => (x, y, rest) => { @@ -880,3 +893,14 @@ export const createMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typ }) }) }) + +export const createMultiplyNumber = /* #__PURE__ */ factory( + name, ['typed'], ({ typed }) => { + return typed(name, { + 'number, number': (m, n) => m * n, + 'bigint, bigint': (m, n) => m * n, + 'number | bigint, Array': deepMultiply, + 'Array, number | bigint': (A, n) => deepMultiply(n, A) + }) + } +) diff --git a/src/function/arithmetic/nthRoot.js b/src/function/arithmetic/nthRoot.js index abcbaeaf3f..8ec27ce3e0 100644 --- a/src/function/arithmetic/nthRoot.js +++ b/src/function/arithmetic/nthRoot.js @@ -9,18 +9,17 @@ import { nthRootNumber } from '../../plain/number/index.js' const name = 'nthRoot' const dependencies = [ 'typed', - 'matrix', + 'DenseMatrix', 'equalScalar', - 'BigNumber', - 'concat' + 'BigNumber' ] -export const createNthRoot = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, BigNumber, concat }) => { +export const createNthRoot = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix, equalScalar, BigNumber }) => { const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) const matAlgo06xS0S0 = createMatAlgo06xS0S0({ typed, equalScalar }) const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Calculate the nth root of a value. @@ -69,7 +68,7 @@ export const createNthRoot = /* #__PURE__ */ factory(name, dependencies, ({ type 'Complex, number': complexErr, Array: typed.referTo('DenseMatrix,number', selfDn => - x => selfDn(matrix(x), 2).valueOf()), + x => selfDn(new DenseMatrix(x), 2).valueOf()), DenseMatrix: typed.referTo('DenseMatrix,number', selfDn => x => selfDn(x, 2)), SparseMatrix: typed.referTo('SparseMatrix,number', selfSn => @@ -98,7 +97,7 @@ export const createNthRoot = /* #__PURE__ */ factory(name, dependencies, ({ type }), 'Array, SparseMatrix': typed.referTo('DenseMatrix,SparseMatrix', selfDS => - (x, y) => selfDS(matrix(x), y)), + (x, y) => selfDS(new DenseMatrix(x), y)), 'number | BigNumber, SparseMatrix': typed.referToSelf(self => (x, y) => { // density must be one (no zeros in matrix) diff --git a/src/function/arithmetic/one.js b/src/function/arithmetic/one.js new file mode 100644 index 0000000000..0ee073d956 --- /dev/null +++ b/src/function/arithmetic/one.js @@ -0,0 +1,75 @@ +import { factory } from '../../utils/factory.js' +import { extend } from '../../utils/object.js' + +const name = 'one' +const dependencies = [ + 'typed', '?BigNumber', '?Complex', '?Fraction', 'size', 'identity' +] +export const createOneNumber = /* #__PURE__ */ factory( + name, ['typed'], ({ typed }) => { + return typed(name, { + number: () => 1 + }) + }) + +export const createOneUnitless = /* #__PURE__ */ factory( + 'oneUnitless', dependencies, ({ + typed, BigNumber, Complex, Fraction, size, identity + }) => { + /** + * Like one() but doesn't handle units, to break circularity + */ + return typed('oneUnitless', { + number: () => 1, + bigint: () => 1n, + BigNumber: () => new BigNumber(1), + Complex: () => new Complex(1), + Fraction: () => new Fraction(1), + boolean: () => true, + Array: A => { + const sz = size(A) + if (sz.length === 2 && sz[0] === sz[1]) { + return identity(sz[0]).valueOf() + } + throw new Error(`No identity of size ${sz}`) + }, + Matrix: M => { + const sz = size(M) + if (sz.length === 2 && sz[0] === sz[1]) return identity(sz[0]) + throw new Error(`No identity matrix of size ${sz}`) + } + }) + }) + +export const createOne = /* #__PURE__ */ factory( + name, ['typed', 'oneUnitless', '?unit'], ({ + typed, oneUnitless, unit + }) => { + /** + * Return the multiplicative identity of the same type as the argument. + * + * Syntax: + * + * math.one(x) + * + * Examples: + * + * math.one(1.618) // returns 1 + * math.one(math.bignumber(222)) // BigNumber 1 + * math.one(math.fraction(1, 3)) // Fraction 1 + * math.one(math.evaluate('0 + 2i')) // Complex 1+0i + * math.one([[2, 3], [4, 5]]) // [[1, 0], [0,1]] + * + * See also: + * typeOf, numeric, zero + * + * @param {MathType} x Any entity mathjs understands + * @return {MathType} Multiplicative identity of same type as x + */ + return typed(name, extend({ + Unit: u => { + if (u.value === undefined || u.value === null) return unit(1) + return oneUnitless(u.value) + } + }, oneUnitless.signatures)) + }) diff --git a/src/function/arithmetic/pow.js b/src/function/arithmetic/pow.js index f7dfba37d5..2ec83bd871 100644 --- a/src/function/arithmetic/pow.js +++ b/src/function/arithmetic/pow.js @@ -9,14 +9,13 @@ const dependencies = [ 'config', 'identity', 'multiply', - 'matrix', 'inv', 'fraction', 'number', 'Complex' ] -export const createPow = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, identity, multiply, matrix, inv, number, fraction, Complex }) => { +export const createPow = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, identity, multiply, inv, number, fraction, Complex }) => { /** * Calculates the power of x to y, `x ^ y`. * @@ -206,6 +205,6 @@ export const createPow = /* #__PURE__ */ factory(name, dependencies, ({ typed, c * @private */ function _powMatrix (x, y) { - return matrix(_powArray(x.valueOf(), y)) + return x.create(_powArray(x.valueOf(), y)) } }) diff --git a/src/function/arithmetic/round.js b/src/function/arithmetic/round.js index 738559ce80..a3a827e94f 100644 --- a/src/function/arithmetic/round.js +++ b/src/function/arithmetic/round.js @@ -13,14 +13,16 @@ const name = 'round' const dependencies = [ 'typed', 'config', - 'matrix', 'equalScalar', - 'zeros', + 'isZero', 'BigNumber', - 'DenseMatrix' + 'DenseMatrix', + 'sparse' ] -export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, matrix, equalScalar, zeros, BigNumber, DenseMatrix }) => { +export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ + typed, config, matrix, equalScalar, isZero, BigNumber, DenseMatrix, sparse +}) => { const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) const matAlgo14xDs = createMatAlgo14xDs({ typed }) @@ -175,36 +177,29 @@ export const createRound = /* #__PURE__ */ factory(name, dependencies, ({ typed, return matAlgo11xS0s(x, n, self, false) }), - 'DenseMatrix, number | BigNumber': typed.referToSelf(self => (x, n) => { + 'Matrix, number | BigNumber': typed.referToSelf(self => (x, n) => { return matAlgo14xDs(x, n, self, false) }), 'Array, number | BigNumber': typed.referToSelf(self => (x, n) => { // use matrix implementation - return matAlgo14xDs(matrix(x), n, self, false).valueOf() + return matAlgo14xDs(new DenseMatrix(x), n, self, false).valueOf() }), - 'number | Complex | BigNumber | Fraction, SparseMatrix': typed.referToSelf(self => (x, n) => { - // check scalar is zero - if (equalScalar(x, 0)) { - // do not execute algorithm, result will be a zero matrix - return zeros(n.size(), n.storage()) + 'number | Complex | BigNumber | Fraction, SparseMatrix': typed.referToSelf( + self => (x, n) => { + const dense = matAlgo12xSfs(n, x, self, true) + if (isZero(x)) return sparse(dense) + return dense } - return matAlgo12xSfs(n, x, self, true) - }), + ), - 'number | Complex | BigNumber | Fraction, DenseMatrix': typed.referToSelf(self => (x, n) => { - // check scalar is zero - if (equalScalar(x, 0)) { - // do not execute algorithm, result will be a zero matrix - return zeros(n.size(), n.storage()) - } - return matAlgo14xDs(n, x, self, true) - }), + 'number | Complex | BigNumber | Fraction, DenseMatrix': typed.referToSelf( + self => (x, n) => matAlgo14xDs(n, x, self, true)), 'number | Complex | BigNumber | Fraction, Array': typed.referToSelf(self => (x, n) => { // use matrix implementation - return matAlgo14xDs(matrix(n), x, self, true).valueOf() + return matAlgo14xDs(new DenseMatrix(n), x, self, true).valueOf() }) }) }) diff --git a/src/function/arithmetic/scalarDivide.js b/src/function/arithmetic/scalarDivide.js new file mode 100644 index 0000000000..15ecfd62c2 --- /dev/null +++ b/src/function/arithmetic/scalarDivide.js @@ -0,0 +1,111 @@ +import { factory } from '../../utils/factory.js' +import { isComplex, isMatrix, isUnit } from '../../utils/is.js' + +const name = 'scalarDivide' +const dependencies = [ + 'typed', '?Unit', 'map', 'multiply', 'equal', 'deepEqual', + 'isInteger', 'isNumeric', 'isZero', + 'abs', 'add', 'divide', '?fraction' +] + +export const createScalarDivide = /* #__PURE__ */ factory(name, dependencies, ({ + typed, map, multiply, equal, deepEqual, + isInteger, isNumeric, isZero, + abs, add, divide, fraction +}) => { + const isScalar = x => isNumeric(x) || isComplex(x) || isUnit(x) + + /** + * Determine what scalar multiple one entity is of another, if it is. + * For scalar arguments, this function is essentially the same as `divide`, + * except that it will always look for a result `r` of a more inclusive + * type to solve `x = r*y` if there is no such result of the same type + * as `y`. + * + * For collection arguments, this function first does a scalar divide + * on the first nonzero entries of the collections, producing `r`, + * and then checks whether the first argument is `r` times the second. If + * so, it returns `r`, otherwise it returns undefined. + * + * Syntax: + * + * math.scalarDivide(x, y) + * + * Examples: + * + * math.scalarDivide(8, 2) // returns 4 + * math.scalarDivide(12n, 3n) // returns 4n + * math.scalarDivide(11n, 3n) // Fraction 11,3 + * math.scalarDivide(0, math.bignumber(7)) // returns 0 + * math.scalarDivide(3, math.fraction(0)) // undefined + * math.scalarDivide([6, 9], [4, 6]) // returns 1.5 + * math.scalarDivide([6, 10], [4, 6]) // undefined + * + * See also: + * + * divide, dotDivide + * + * @param {MathType} x Numerator + * @param {MathType} y Denominator + * @return {MathScalarType | undefined} scalar coefficient or undefined if not a scalar multiple + */ + return typed(name, { + 'Array | Matrix, Array | Matrix': typed.referToSelf(self => (x, y) => { + if (isMatrix(x)) x = x.valueOf() + if (isMatrix(y)) y = y.valueOf() + if (x.length === 0) { + if (y.length === 0) return 0 + else return undefined + } + if (y.length === 0) return undefined + let initialx = 0 + let initialy = 0 + let foundInit = false + map(x, y, (eltx, elty) => { + if (foundInit) return 0 + if (isZero(eltx) && isZero(elty)) return 0 + initialx = eltx + initialy = elty + foundInit = true + return 1 + }) + const initialr = self(initialx, initialy) + if (initialr === undefined) return undefined + if (deepEqual(multiply(initialr, y), x)) return initialr + return undefined + }), + 'Array | Matrix, any': () => undefined, + 'any, Array | Matrix': () => undefined, + 'any, any': (x, y) => { + if (!isScalar(x) || !isScalar(y)) return undefined + if (isZero(y)) { + if (isZero(x)) { + if (isUnit(x)) { + if (isUnit(y)) { + const y1 = y.clone() + y1.value = 1 + return divide(x, y) + } else return divide(x, add(y, 1)) + } return x + } else return undefined + } + if (isZero(x)) y = abs(y) // avoid `-0` + if (typeof y === 'bigint' && fraction) { + const quotient = fraction(x, y) + if (isInteger(quotient)) return quotient.n + return quotient + } + const quotient = divide(x, y) + if (isNumeric(quotient)) return quotient + if (isComplex(quotient)) { + if (equal(quotient.re + quotient.im, quotient.re)) return quotient.re + return quotient + } + if (isUnit(quotient)) { + if (quotient.unitless()) return quotient.value + return quotient + } + return undefined + } + }) +}) diff --git a/src/function/arithmetic/subtract.js b/src/function/arithmetic/subtract.js index aa6732484b..5c546604b0 100644 --- a/src/function/arithmetic/subtract.js +++ b/src/function/arithmetic/subtract.js @@ -9,7 +9,6 @@ import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgori const name = 'subtract' const dependencies = [ 'typed', - 'matrix', 'equalScalar', 'subtractScalar', 'unaryMinus', @@ -17,7 +16,7 @@ const dependencies = [ 'concat' ] -export const createSubtract = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, subtractScalar, unaryMinus, DenseMatrix, concat }) => { +export const createSubtract = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar, subtractScalar, unaryMinus, DenseMatrix, concat }) => { // TODO: split function subtract in two: subtract and subtractScalar const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) @@ -25,7 +24,7 @@ export const createSubtract = /* #__PURE__ */ factory(name, dependencies, ({ typ const matAlgo05xSfSf = createMatAlgo05xSfSf({ typed, equalScalar }) const matAlgo10xSids = createMatAlgo10xSids({ typed, DenseMatrix }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Subtract two values, `x - y`. @@ -60,7 +59,30 @@ export const createSubtract = /* #__PURE__ */ factory(name, dependencies, ({ typ return typed( name, { - 'any, any': subtractScalar + 'any, any': subtractScalar, + + 'Range, Range': typed.referToSelf(self => (r, p) => { + if (r.for !== p.for) throw new Error('Range length mismatch') + return r.createRange({ + start: self(r.start, p.start), + length: r.length, + step: self(r.step, p.step) + }) + }), + 'Range, Matrix': typed.referToSelf( + self => (r, m) => self(r.valueOf(), m)), + 'Range, any': typed.referToSelf(self => (r, s) => r.createRange({ + start: self(r.start, s), + length: r.length, + step: r.step + })), + 'Matrix, Range': typed.referToSelf( + self => (m, r) => self(m, r.valueOf())), + 'any, Range': typed.referToSelf(self => (s, r) => r.createRange({ + start: self(s, r.start), + length: r.length, + step: unaryMinus(r.step) + })) }, matrixAlgorithmSuite({ elop: subtractScalar, diff --git a/src/function/arithmetic/zero.js b/src/function/arithmetic/zero.js new file mode 100644 index 0000000000..8bc6037c8f --- /dev/null +++ b/src/function/arithmetic/zero.js @@ -0,0 +1,64 @@ +import { factory } from '../../utils/factory.js' + +const name = 'zero' +const dependencies = [ + 'typed', '?BigNumber', '?Complex', '?Fraction', '?unit' +] + +export const createZeroNumber = /* #__PURE__ */ factory( + name, ['typed'], ({ typed }) => { + return typed(name, { number: () => 0 }) + }) + +export const createZero = /* #__PURE__ */ factory(name, dependencies, ({ + typed, BigNumber, Complex, Fraction, unit +}) => { + /** + * Return the additive identity of the same type as the argument. + * + * Syntax: + * + * math.zero(x) + * + * Examples: + * + * math.zero(1.618) // returns 0 + * math.zero(math.bignumber(222)) // BigNumber 0 + * math.zero(math.fraction(1, 3)) // Fraction 0 + * math.zero(math.evaluate('0 + 2i')) // Complex 0+0i + * math.zero([[2, 3, 4], [4, 5, 6]]) // [[0, 0, 0], [0, 0, 0]] + * + * See also: + * typeOf, numeric, one + * + * @param {MathType} x Any entity mathjs understands + * @return {MathType} Additive identity of same type as x + */ + return typed(name, { + number: () => 0, + bigint: () => 0n, + BigNumber: () => new BigNumber(0), + Complex: () => new Complex(0), + Fraction: () => new Fraction(0), + boolean: () => false, + Unit: typed.referToSelf(self => u => { + // want 0 of the same units and value type as u + const result = u.clone() + result.value = self(u.value) + return result + }), + Array: typed.referToSelf(self => A => _zeroArray(A, self)), + Range: typed.referToSelf(self => R => { + const z = self(R.start) + return R.createRange({ start: z, step: z, length: R.length }) + }), + // TODO: there should be a way to create the all-zero sparse matrix that + // does not involve constructing an array of all zeros + Matrix: typed.referToSelf(self => M => + M.create(_zeroArray(M.valueOf(), self))) + }) + + function _zeroArray (A, zeroer) { + return A.map(elt => zeroer(elt)) + } +}) diff --git a/src/function/bitwise/bitAnd.js b/src/function/bitwise/bitAnd.js index d6e169b09e..080a176aae 100644 --- a/src/function/bitwise/bitAnd.js +++ b/src/function/bitwise/bitAnd.js @@ -9,16 +9,15 @@ import { bitAndNumber } from '../../plain/number/index.js' const name = 'bitAnd' const dependencies = [ 'typed', - 'matrix', - 'equalScalar', - 'concat' + 'DenseMatrix', + 'equalScalar' ] -export const createBitAnd = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, concat }) => { +export const createBitAnd = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix, equalScalar }) => { const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) const matAlgo06xS0S0 = createMatAlgo06xS0S0({ typed, equalScalar }) const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Bitwise AND two values, `x & y`. diff --git a/src/function/bitwise/bitOr.js b/src/function/bitwise/bitOr.js index c0f7dea215..45f98376f6 100644 --- a/src/function/bitwise/bitOr.js +++ b/src/function/bitwise/bitOr.js @@ -9,17 +9,15 @@ import { bitOrNumber } from '../../plain/number/index.js' const name = 'bitOr' const dependencies = [ 'typed', - 'matrix', 'equalScalar', - 'DenseMatrix', - 'concat' + 'DenseMatrix' ] -export const createBitOr = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, DenseMatrix, concat }) => { +export const createBitOr = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar, DenseMatrix }) => { const matAlgo01xDSid = createMatAlgo01xDSid({ typed }) const matAlgo04xSidSid = createMatAlgo04xSidSid({ typed, equalScalar }) const matAlgo10xSids = createMatAlgo10xSids({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Bitwise OR two values, `x | y`. diff --git a/src/function/bitwise/bitXor.js b/src/function/bitwise/bitXor.js index a46acacc8a..75e1130a37 100644 --- a/src/function/bitwise/bitXor.js +++ b/src/function/bitwise/bitXor.js @@ -9,17 +9,15 @@ import { bitXorNumber } from '../../plain/number/index.js' const name = 'bitXor' const dependencies = [ 'typed', - 'matrix', 'DenseMatrix', - 'concat', 'SparseMatrix' ] -export const createBitXor = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, DenseMatrix, concat, SparseMatrix }) => { +export const createBitXor = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix, SparseMatrix }) => { const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) const matAlgo07xSSf = createMatAlgo07xSSf({ typed, SparseMatrix }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Bitwise XOR two values, `x ^ y`. diff --git a/src/function/bitwise/leftShift.js b/src/function/bitwise/leftShift.js index 18b406fc61..a84ab91796 100644 --- a/src/function/bitwise/leftShift.js +++ b/src/function/bitwise/leftShift.js @@ -13,22 +13,20 @@ import { leftShiftBigNumber } from '../../utils/bignumber/bitwise.js' const name = 'leftShift' const dependencies = [ 'typed', - 'matrix', 'equalScalar', 'zeros', - 'DenseMatrix', - 'concat' + 'DenseMatrix' ] -export const createLeftShift = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, zeros, DenseMatrix, concat }) => { +export const createLeftShift = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar, zeros, DenseMatrix }) => { 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, concat }) - const useMatrixForArrayScalar = createUseMatrixForArrayScalar({ typed, matrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) + const useMatrixForArrayScalar = createUseMatrixForArrayScalar({ typed, DenseMatrix }) /** * Bitwise left logical shift of a value x by y number of bits, `x << y`. diff --git a/src/function/bitwise/rightArithShift.js b/src/function/bitwise/rightArithShift.js index c84a411d06..2747b973c4 100644 --- a/src/function/bitwise/rightArithShift.js +++ b/src/function/bitwise/rightArithShift.js @@ -13,22 +13,20 @@ import { rightArithShiftNumber } from '../../plain/number/index.js' const name = 'rightArithShift' const dependencies = [ 'typed', - 'matrix', 'equalScalar', 'zeros', - 'DenseMatrix', - 'concat' + 'DenseMatrix' ] -export const createRightArithShift = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, zeros, DenseMatrix, concat }) => { +export const createRightArithShift = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar, zeros, DenseMatrix }) => { 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, concat }) - const useMatrixForArrayScalar = createUseMatrixForArrayScalar({ typed, matrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) + const useMatrixForArrayScalar = createUseMatrixForArrayScalar({ typed, DenseMatrix }) /** * Bitwise right arithmetic shift of a value x by y number of bits, `x >> y`. diff --git a/src/function/bitwise/rightLogShift.js b/src/function/bitwise/rightLogShift.js index 55cef7d355..286e201343 100644 --- a/src/function/bitwise/rightLogShift.js +++ b/src/function/bitwise/rightLogShift.js @@ -12,22 +12,20 @@ import { createUseMatrixForArrayScalar } from './useMatrixForArrayScalar.js' const name = 'rightLogShift' const dependencies = [ 'typed', - 'matrix', 'equalScalar', 'zeros', - 'DenseMatrix', - 'concat' + 'DenseMatrix' ] -export const createRightLogShift = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, zeros, DenseMatrix, concat }) => { +export const createRightLogShift = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar, zeros, DenseMatrix }) => { 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, concat }) - const useMatrixForArrayScalar = createUseMatrixForArrayScalar({ typed, matrix }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) + const useMatrixForArrayScalar = createUseMatrixForArrayScalar({ typed, DenseMatrix }) /** * Bitwise right logical shift of value x by y number of bits, `x >>> y`. diff --git a/src/function/bitwise/useMatrixForArrayScalar.js b/src/function/bitwise/useMatrixForArrayScalar.js index ff7f231e2f..a039ab828c 100644 --- a/src/function/bitwise/useMatrixForArrayScalar.js +++ b/src/function/bitwise/useMatrixForArrayScalar.js @@ -1,15 +1,15 @@ import { factory } from '../../utils/factory.js' -export const createUseMatrixForArrayScalar = /* #__PURE__ */ factory('useMatrixForArrayScalar', ['typed', 'matrix'], ({ typed, matrix }) => ({ +export const createUseMatrixForArrayScalar = /* #__PURE__ */ factory('useMatrixForArrayScalar', ['typed', 'DenseMatrix'], ({ typed, DenseMatrix }) => ({ 'Array, number': typed.referTo('DenseMatrix, number', - selfDn => (x, y) => selfDn(matrix(x), y).valueOf()), + selfDn => (x, y) => selfDn(new DenseMatrix(x), y).valueOf()), 'Array, BigNumber': typed.referTo('DenseMatrix, BigNumber', - selfDB => (x, y) => selfDB(matrix(x), y).valueOf()), + selfDB => (x, y) => selfDB(new DenseMatrix(x), y).valueOf()), 'number, Array': typed.referTo('number, DenseMatrix', - selfnD => (x, y) => selfnD(x, matrix(y)).valueOf()), + selfnD => (x, y) => selfnD(x, new DenseMatrix(y)).valueOf()), 'BigNumber, Array': typed.referTo('BigNumber, DenseMatrix', - selfBD => (x, y) => selfBD(x, matrix(y)).valueOf()) + selfBD => (x, y) => selfBD(x, new DenseMatrix(y)).valueOf()) })) diff --git a/src/function/logical/and.js b/src/function/logical/and.js index 827751d042..dc7ef692cd 100644 --- a/src/function/logical/and.js +++ b/src/function/logical/and.js @@ -9,19 +9,18 @@ import { andNumber } from '../../plain/number/index.js' const name = 'and' const dependencies = [ 'typed', - 'matrix', + 'DenseMatrix', 'equalScalar', 'zeros', - 'not', - 'concat' + 'not' ] -export const createAnd = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, zeros, not, concat }) => { +export const createAnd = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix, equalScalar, zeros, not }) => { const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar }) const matAlgo06xS0S0 = createMatAlgo06xS0S0({ typed, equalScalar }) const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar }) const matAlgo14xDs = createMatAlgo14xDs({ typed }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Logical `and`. Test whether two values are both defined with a nonzero/nonempty value. @@ -107,12 +106,12 @@ export const createAnd = /* #__PURE__ */ factory(name, dependencies, ({ typed, m 'Array, any': typed.referToSelf(self => (x, y) => { // use matrix implementation - return self(matrix(x), y).valueOf() + return self(new DenseMatrix(x), y).valueOf() }), 'any, Array': typed.referToSelf(self => (x, y) => { // use matrix implementation - return self(x, matrix(y)).valueOf() + return self(x, new DenseMatrix(y)).valueOf() }) }, matrixAlgorithmSuite({ diff --git a/src/function/logical/nullish.js b/src/function/logical/nullish.js index cd3a7af52d..1005097cce 100644 --- a/src/function/logical/nullish.js +++ b/src/function/logical/nullish.js @@ -5,15 +5,15 @@ import { createMatAlgo13xDD } from '../../type/matrix/utils/matAlgo13xDD.js' import { DimensionError } from '../../error/DimensionError.js' const name = 'nullish' -const dependencies = ['typed', 'matrix', 'size', 'flatten', 'deepEqual'] +const dependencies = ['typed', 'DenseMatrix', 'size', 'flatten', 'deepEqual'] export const createNullish = /* #__PURE__ */ factory( name, dependencies, - ({ typed, matrix, size, flatten, deepEqual }) => { + ({ typed, DenseMatrix, size, flatten, deepEqual }) => { const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) - const matAlgo14xDs = createMatAlgo14xDs({ typed }) const matAlgo13xDD = createMatAlgo13xDD({ typed }) + const matAlgo14xDs = createMatAlgo14xDs({ typed }) /** * Nullish coalescing operator (??). Returns the right-hand side operand @@ -66,14 +66,14 @@ export const createNullish = /* #__PURE__ */ factory( // DenseMatrix-first handlers (no broadcasting between collections) 'DenseMatrix, DenseMatrix': typed.referToSelf(self => (x, y) => matAlgo13xDD(x, y, self)), 'DenseMatrix, SparseMatrix': typed.referToSelf(self => (x, y) => matAlgo03xDSf(x, y, self, false)), - 'DenseMatrix, Array': typed.referToSelf(self => (x, y) => matAlgo13xDD(x, matrix(y), self)), + 'DenseMatrix, Array': typed.referToSelf(self => (x, y) => matAlgo13xDD(x, new DenseMatrix(y), self)), 'DenseMatrix, any': typed.referToSelf(self => (x, y) => matAlgo14xDs(x, y, self, false)), // Array-first handlers (bridge via matrix() where needed) - 'Array, Array': typed.referToSelf(self => (x, y) => matAlgo13xDD(matrix(x), matrix(y), self).valueOf()), - 'Array, DenseMatrix': typed.referToSelf(self => (x, y) => matAlgo13xDD(matrix(x), y, self)), - 'Array, SparseMatrix': typed.referToSelf(self => (x, y) => matAlgo03xDSf(matrix(x), y, self, false)), - 'Array, any': typed.referToSelf(self => (x, y) => matAlgo14xDs(matrix(x), y, self, false).valueOf()) + 'Array, Array': typed.referToSelf(self => (x, y) => matAlgo13xDD(new DenseMatrix(x), new DenseMatrix(y), self).valueOf()), + 'Array, DenseMatrix': typed.referToSelf(self => (x, y) => matAlgo13xDD(new DenseMatrix(x), y, self)), + 'Array, SparseMatrix': typed.referToSelf(self => (x, y) => matAlgo03xDSf(new DenseMatrix(x), y, self, false)), + 'Array, any': typed.referToSelf(self => (x, y) => matAlgo14xDs(new DenseMatrix(x), y, self, false).valueOf()) } ) } diff --git a/src/function/logical/or.js b/src/function/logical/or.js index 12d6c5b48d..694e1f8768 100644 --- a/src/function/logical/or.js +++ b/src/function/logical/or.js @@ -8,17 +8,15 @@ import { orNumber } from '../../plain/number/index.js' const name = 'or' const dependencies = [ 'typed', - 'matrix', 'equalScalar', - 'DenseMatrix', - 'concat' + 'DenseMatrix' ] -export const createOr = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, DenseMatrix, concat }) => { +export const createOr = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar, DenseMatrix }) => { const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) const matAlgo05xSfSf = createMatAlgo05xSfSf({ typed, equalScalar }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Logical `or`. Test if at least one value is defined with a nonzero/nonempty value. diff --git a/src/function/logical/xor.js b/src/function/logical/xor.js index 1ae3a05f5b..078c6716c7 100644 --- a/src/function/logical/xor.js +++ b/src/function/logical/xor.js @@ -8,9 +8,7 @@ import { xorNumber } from '../../plain/number/index.js' const name = 'xor' const dependencies = [ 'typed', - 'matrix', 'DenseMatrix', - 'concat', 'SparseMatrix' ] @@ -18,7 +16,7 @@ export const createXor = /* #__PURE__ */ factory(name, dependencies, ({ typed, m const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) const matAlgo07xSSf = createMatAlgo07xSSf({ typed, SparseMatrix }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Logical `xor`. Test whether one and only one value is defined with a nonzero/nonempty value. diff --git a/src/function/matrix/concat.js b/src/function/matrix/concat.js index 6d8fe0f44b..fbd3d7f537 100644 --- a/src/function/matrix/concat.js +++ b/src/function/matrix/concat.js @@ -6,9 +6,9 @@ import { DimensionError } from '../../error/DimensionError.js' import { factory } from '../../utils/factory.js' const name = 'concat' -const dependencies = ['typed', 'matrix', 'isInteger'] +const dependencies = ['typed', 'isInteger'] -export const createConcat = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, isInteger }) => { +export const createConcat = /* #__PURE__ */ factory(name, dependencies, ({ typed, isInteger }) => { /** * Concatenate two or more matrices. * @@ -44,6 +44,7 @@ export const createConcat = /* #__PURE__ */ factory(name, dependencies, ({ typed let i const len = args.length let dim = -1 // zero-based dimension + let matrixMaker let prevDim let asMatrix = false const matrices = [] // contains multi dimensional arrays @@ -54,6 +55,7 @@ export const createConcat = /* #__PURE__ */ factory(name, dependencies, ({ typed // test whether we need to return a Matrix (if not we return an Array) if (isMatrix(arg)) { asMatrix = true + matrixMaker = arg } if (isNumber(arg) || isBigNumber(arg)) { @@ -97,7 +99,7 @@ export const createConcat = /* #__PURE__ */ factory(name, dependencies, ({ typed res = _concat(res, matrices.shift(), dim) } - return asMatrix ? matrix(res) : res + return asMatrix ? matrixMaker.create(res) : res }, '...string': function (args) { diff --git a/src/function/matrix/det.js b/src/function/matrix/det.js index 8c2123fa6e..f3bb8f7c50 100644 --- a/src/function/matrix/det.js +++ b/src/function/matrix/det.js @@ -4,9 +4,9 @@ import { format } from '../../utils/string.js' import { factory } from '../../utils/factory.js' const name = 'det' -const dependencies = ['typed', 'matrix', 'subtractScalar', 'multiply', 'divideScalar', 'isZero', 'unaryMinus'] +const dependencies = ['typed', 'DenseMatrix', 'subtractScalar', 'multiply', 'divideScalar', 'isZero', 'unaryMinus'] -export const createDet = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, subtractScalar, multiply, divideScalar, isZero, unaryMinus }) => { +export const createDet = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix, subtractScalar, multiply, divideScalar, isZero, unaryMinus }) => { /** * Calculate the determinant of a matrix. * @@ -42,7 +42,7 @@ export const createDet = /* #__PURE__ */ factory(name, dependencies, ({ typed, m if (isMatrix(x)) { size = x.size() } else if (Array.isArray(x)) { - x = matrix(x) + x = new DenseMatrix(x) size = x.size() } else { // a scalar diff --git a/src/function/matrix/diag.js b/src/function/matrix/diag.js index a0f9bc4d47..b6e849a0c1 100644 --- a/src/function/matrix/diag.js +++ b/src/function/matrix/diag.js @@ -127,6 +127,7 @@ export const createDiag = /* #__PURE__ */ factory(name, dependencies, ({ typed, // matrix size const ms = [l + kSub, l + kSuper] + if (format === 'range') format = 'dense' if (format && format !== 'sparse' && format !== 'dense') { throw new TypeError(`Unknown matrix type ${format}"`) } diff --git a/src/function/matrix/identity.js b/src/function/matrix/identity.js index 5ee1cf0bc6..0642e4c5c7 100644 --- a/src/function/matrix/identity.js +++ b/src/function/matrix/identity.js @@ -7,13 +7,12 @@ const name = 'identity' const dependencies = [ 'typed', 'config', - 'matrix', 'BigNumber', 'DenseMatrix', 'SparseMatrix' ] -export const createIdentity = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, matrix, BigNumber, DenseMatrix, SparseMatrix }) => { +export const createIdentity = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, BigNumber, DenseMatrix, SparseMatrix }) => { /** * Create a 2-dimensional identity matrix with size m x n or n x n. * The matrix has ones on the diagonal and zeros elsewhere. @@ -46,11 +45,11 @@ export const createIdentity = /* #__PURE__ */ factory(name, dependencies, ({ typ */ return typed(name, { '': function () { - return (config.matrix === 'Matrix') ? matrix([]) : [] + return (config.matrix === 'Matrix') ? new DenseMatrix([]) : [] }, string: function (format) { - return matrix(format) + return _identity(0, 0, format) }, 'number | BigNumber': function (rows) { @@ -88,7 +87,7 @@ export const createIdentity = /* #__PURE__ */ factory(name, dependencies, ({ typ function _identityVector (size, format) { switch (size.length) { - case 0: return format ? matrix(format) : [] + case 0: return _identity(0, 0, format) case 1: return _identity(size[0], size[0], format) case 2: return _identity(size[0], size[1], format) default: throw new Error('Vector containing two values expected') @@ -112,11 +111,16 @@ export const createIdentity = /* #__PURE__ */ factory(name, dependencies, ({ typ if (isBigNumber(rows)) rows = rows.toNumber() if (isBigNumber(cols)) cols = cols.toNumber() - if (!isInteger(rows) || rows < 1) { - throw new Error('Parameters in function identity must be positive integers') + if (!isInteger(rows) || rows < 0) { + throw new Error('Parameters in function identity must be nonnegative integers') } - if (!isInteger(cols) || cols < 1) { - throw new Error('Parameters in function identity must be positive integers') + if (!isInteger(cols) || cols < 0) { + throw new Error('Parameters in function identity must be nonnegative integers') + } + + if (rows === 0 || cols === 0) { + if (!format) return [] + return format === 'sparse' ? new SparseMatrix() : new DenseMatrix() } const one = Big ? new BigNumber(1) : 1 diff --git a/src/function/matrix/inv.js b/src/function/matrix/inv.js index 5065fce27e..060e4b1ca9 100644 --- a/src/function/matrix/inv.js +++ b/src/function/matrix/inv.js @@ -6,7 +6,6 @@ import { format } from '../../utils/string.js' const name = 'inv' const dependencies = [ 'typed', - 'matrix', 'divideScalar', 'addScalar', 'multiply', @@ -45,7 +44,7 @@ export const createInv = /* #__PURE__ */ factory(name, dependencies, ({ typed, m // vector if (size[0] === 1) { if (isMatrix(x)) { - return matrix([ + return x.create([ divideScalar(1, x.valueOf()[0]) ]) } else { @@ -65,10 +64,7 @@ export const createInv = /* #__PURE__ */ factory(name, dependencies, ({ typed, m const cols = size[1] if (rows === cols) { if (isMatrix(x)) { - return matrix( - _inv(x.valueOf(), rows, cols), - x.storage() - ) + return x.create(_inv(x.valueOf(), rows, cols)) } else { // return an Array return _inv(x, rows, cols) diff --git a/src/function/matrix/matrixFromRows.js b/src/function/matrix/matrixFromRows.js index e8eaf8178c..2aad9b2e0e 100644 --- a/src/function/matrix/matrixFromRows.js +++ b/src/function/matrix/matrixFromRows.js @@ -24,7 +24,7 @@ export const createMatrixFromRows = /* #__PURE__ */ factory(name, dependencies, * matrix, matrixFromColumns, matrixFromFunction, zeros * * @param {... Array | Matrix} rows Multiple rows - * @return { number[][] | Matrix } if at least one of the arguments is an array, an array will be returned + * @return { number[][] | Matrix } if at least one of the arguments is an Matrix, a Matrix will be returned */ return typed(name, { '...Array': function (arr) { diff --git a/src/function/matrix/ones.js b/src/function/matrix/ones.js index 5bd55b7719..c000b88085 100644 --- a/src/function/matrix/ones.js +++ b/src/function/matrix/ones.js @@ -4,9 +4,13 @@ import { resize } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'ones' -const dependencies = ['typed', 'config', 'matrix', 'BigNumber'] +const dependencies = [ + 'typed', 'config', 'matrix', 'BigNumber', 'Range', 'zero' +] -export const createOnes = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, matrix, BigNumber }) => { +export const createOnes = /* #__PURE__ */ factory(name, dependencies, ({ + typed, config, matrix, BigNumber, Range, zero +}) => { /** * Create a matrix filled with ones. The created matrix can have one or * multiple dimensions. @@ -88,6 +92,23 @@ export const createOnes = /* #__PURE__ */ factory(name, dependencies, ({ typed, if (format) { // return a matrix + if (format === 'range') { + if (size.length === 0) { + return new Range({ start: defaultValue, length: 0 }) + } + if (size.length === 1) { + return new Range({ + start: defaultValue, + length: size[0], + step: zero(defaultValue) + }) + } + return new Range({ + start: _ones(size.slice(1), format), + length: size[0], + step: zero(defaultValue) + }) + } const m = matrix(format) if (size.length > 0) { return m.resize(size, defaultValue) diff --git a/src/function/matrix/range.js b/src/function/matrix/range.js index d919eeb76a..a27ec26e77 100644 --- a/src/function/matrix/range.js +++ b/src/function/matrix/range.js @@ -1,14 +1,21 @@ import { factory } from '../../utils/factory.js' -import { noBignumber, noMatrix } from '../../utils/noop.js' +import { parseRange } from '../../utils/collection.js' const name = 'range' -const dependencies = ['typed', 'config', '?matrix', '?bignumber', 'equal', 'smaller', 'smallerEq', 'larger', 'largerEq', 'add', 'isZero', 'isPositive'] - -export const createRange = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, matrix, bignumber, smaller, smallerEq, larger, largerEq, add, isZero, isPositive }) => { +export const dependencies = [ + 'typed', 'config', '?Range', '?matrix', '?bignumber', + 'smaller', 'smallerEq', 'larger', 'largerEq', 'add', 'isZero', 'isPositive' +] + +export const createRange = /* #__PURE__ */ factory(name, dependencies, ({ + typed, config, Range, matrix, bignumber, + smaller, smallerEq, larger, largerEq, add, isZero, isPositive +}) => { /** * Create a matrix or array containing a range of values. * By default, the range end is excluded. This can be customized by providing - * an extra parameter `includeEnd`. + * an extra boolean parameter `includeEnd`, or by using the attribute-object + * argument form instead. * * Syntax: * @@ -20,6 +27,7 @@ export const createRange = /* #__PURE__ */ factory(name, dependencies, ({ typed, * // end and a step size of 1. * math.range(start, end, step [, includeEnd]) // Create a range with start, step, * // and end. + * math.range({start?, end?, last?, step?, length?}) // Create a range with given attributes * * Where: * @@ -33,12 +41,28 @@ export const createRange = /* #__PURE__ */ factory(name, dependencies, ({ typed, * Step size. Default value is 1. * - `includeEnd: boolean` * Option to specify whether to include the end or not. False by default. + * Note this parameter is not allowed when the arguments are supplied as + * a plain object of attributes. + * - `{start?, end?, last?, step?, length?}` + * A plain object of attributes with any of the indicated keys, each of + * which is optional. Any unspecified keys will be filled in per the + * [Range documentation](../../../docs/reference/classes/range.md). Note + * in particular in this form, the `end` property always specifies an + * exclusive upper bound and the `last` property specifies an inclusive + * upper bound. + * + * The function returns a `Range` matrix object when the library is + * configured with `config = { matrix: 'Matrix' }, and returns an Array + * otherwise. * - * The function returns a `DenseMatrix` when the library is configured with - * `config = { matrix: 'Matrix' }, and returns an Array otherwise. - * Note that the type of the returned values is taken from the type of the - * provided start/end value. If only one of these is a built-in `number` type, - * it will be promoted to the type of the other endpoint. However, in the case + * Note that the type of the returned values is determined primarily by the + * type(s) of the provided start and step values. Generally speaking, it will + * be the type of the start value if the step is not specified, and the + * type that mathjs returns for `start + step` if both are specified. For + * example, `range(3, bignumber(10), bignumber(1))` will produce a range of + * BigNumbers since `add(3, bignumber(1))` produces a BigNumber, whereas + * `range(3, 10n, 1n)` will produce a range of `number` values because + * `add(3, 1n)` produces a number, by mathjs conversion rules. In the case * of Unit values, both endpoints must have compatible units, and the return * value will have compatible units as well. * @@ -55,140 +79,77 @@ export const createRange = /* #__PURE__ */ factory(name, dependencies, ({ typed, * * ones, zeros, size, subset * - * @param {*} args Parameters describing the range's `start`, `end`, and optional `step`. + * @param {*} args Parameters describing the range's `start`, `end`, and + * optional `step`. * @return {Array | Matrix} range */ + const MathType = 'number|bigint|BigNumber|Fraction|Unit|Array|Matrix' + // Is anything else needed on that list? return typed(name, { - // TODO: simplify signatures when typed-function supports default values and optional arguments + // TODO: simplify signatures a bit futher when typed-function supports + // default values and optional arguments - string: _strRange, - 'string, boolean': _strRange, + // string arguments just get broken up and passed back to range: + string: typed.referToSelf( + self => str => _redispatchStrings(self, str, false)), + 'string, boolean': typed.referToSelf( + self => (str, includeEnd) => _redispatchStrings(self, str, includeEnd)), number: function (oops) { throw new TypeError(`Too few arguments to function range(): ${oops}`) }, boolean: function (oops) { - throw new TypeError(`Unexpected type of argument 1 to function range(): ${oops}, number|bigint|BigNumber|Fraction`) - }, - - 'number, number': function (start, end) { - return _out(_range(start, end, 1, false)) - }, - 'number, number, number': function (start, end, step) { - return _out(_range(start, end, step, false)) - }, - 'number, number, boolean': function (start, end, includeEnd) { - return _out(_range(start, end, 1, includeEnd)) - }, - 'number, number, number, boolean': function (start, end, step, includeEnd) { - return _out(_range(start, end, step, includeEnd)) - }, - - // Handle bigints; if either limit is bigint, range should be too - 'bigint, bigint|number': function (start, end) { - return _out(_range(start, end, 1n, false)) - }, - 'number, bigint': function (start, end) { - return _out(_range(BigInt(start), end, 1n, false)) - }, - 'bigint, bigint|number, bigint|number': function (start, end, step) { - return _out(_range(start, end, BigInt(step), false)) - }, - 'number, bigint, bigint|number': function (start, end, step) { - return _out(_range(BigInt(start), end, BigInt(step), false)) - }, - 'bigint, bigint|number, boolean': function (start, end, includeEnd) { - return _out(_range(start, end, 1n, includeEnd)) + throw new TypeError( + 'Unexpected type of argument 1 to function range(): ' + + `${oops}, number|bigint|BigNumber|Fraction`) }, - 'number, bigint, boolean': function (start, end, includeEnd) { - return _out(_range(BigInt(start), end, 1n, includeEnd)) - }, - 'bigint, bigint|number, bigint|number, boolean': function (start, end, step, includeEnd) { - return _out(_range(start, end, BigInt(step), includeEnd)) - }, - 'number, bigint, bigint|number, boolean': function (start, end, step, includeEnd) { - return _out(_range(BigInt(start), end, BigInt(step), includeEnd)) - }, - - 'BigNumber, BigNumber': function (start, end) { - const BigNumber = start.constructor - - return _out(_range(start, end, new BigNumber(1), false)) - }, - 'BigNumber, BigNumber, BigNumber': function (start, end, step) { - return _out(_range(start, end, step, false)) - }, - 'BigNumber, BigNumber, boolean': function (start, end, includeEnd) { - const BigNumber = start.constructor - return _out(_range(start, end, new BigNumber(1), includeEnd)) - }, - 'BigNumber, BigNumber, BigNumber, boolean': function (start, end, step, includeEnd) { - return _out(_range(start, end, step, includeEnd)) - }, - - 'Fraction, Fraction': function (start, end) { - return _out(_range(start, end, 1, false)) - }, - 'Fraction, Fraction, Fraction': function (start, end, step) { - return _out(_range(start, end, step, false)) - }, - 'Fraction, Fraction, boolean': function (start, end, includeEnd) { - return _out(_range(start, end, 1, includeEnd)) - }, - 'Fraction, Fraction, Fraction, boolean': function (start, end, step, includeEnd) { - return _out(_range(start, end, step, includeEnd)) - }, - - 'Unit, Unit, Unit': function (start, end, step) { - return _out(_range(start, end, step, false)) + Object: obj => { + if (!Range) { + if ('last' in obj) return _range(obj.start, obj.last, obj.step, true) + return _range(obj.start, obj.end, obj.step, false) + } + const rng = new Range(obj) + return config.matrix === 'Array' ? rng.toArray() : rng }, - 'Unit, Unit, Unit, boolean': function (start, end, step, includeEnd) { - return _out(_range(start, end, step, includeEnd)) - } + [`${MathType}, ${MathType}`]: _range, + [`${MathType}, ${MathType}, ${MathType}`]: _range, + [`${MathType}, ${MathType}, boolean`]: + (start, end, includeEnd) => _range(start, end, undefined, includeEnd), + [`${MathType}, ${MathType}, ${MathType}, boolean`]: _range }) - function _out (arr) { - if (config.matrix === 'Matrix') { - return matrix ? matrix(arr) : noMatrix() - } - - return arr - } - - function _strRange (str, includeEnd) { - const r = _parse(str) - if (!r) { - throw new SyntaxError('String "' + str + '" is no valid range') - } - - if (config.number === 'BigNumber') { - if (bignumber === undefined) { - noBignumber() - } - - return _out(_range( - bignumber(r.start), - bignumber(r.end), - bignumber(r.step)), - includeEnd) - } else { - return _out(_range(r.start, r.end, r.step, includeEnd)) + function _redispatchStrings (fullRange, str, includeEnd) { + const fields = parseRange(str) + if (fields === null) { + throw new SyntaxError(`String '${str}' does not represent a range`) } + const step = fields.step || '1' + const start = fields.start || '0' + const end = fields.end || 'Infinity' + // let typed-function handle the strings + const result = fullRange(start, end, step, includeEnd) + return result } /** - * Create a range with numbers or BigNumbers - * @param {number | BigNumber | Unit} start - * @param {number | BigNumber | Unit} end - * @param {number | BigNumber | Unit} step + * Create a range from start, step, end + * @param {MathType} start + * @param {MathType} end + * @param {MathType} step * @param {boolean} includeEnd - * @returns {Array} range + * @returns {Range | Array} range * @private */ - function _range (start, end, step, includeEnd) { + function _range (start, end, step, includeEnd = false) { + if (Range) { + const range = new Range( + includeEnd ? { start, step, last: end } : { start, step, end }) + return config.matrix === 'Array' ? range.toArray() : range + } + // Otherwise we have to make up an Array ourselves: const array = [] if (isZero(step)) throw new Error('Step must be non-zero') const ongoing = isPositive(step) @@ -201,49 +162,4 @@ export const createRange = /* #__PURE__ */ factory(name, dependencies, ({ typed, } return array } - - /** - * Parse a string into a range, - * The string contains the start, optional step, and end, separated by a colon. - * If the string does not contain a valid range, null is returned. - * For example str='0:2:11'. - * @param {string} str - * @return {{start: number, end: number, step: number} | null} range Object containing properties start, end, step - * @private - */ - function _parse (str) { - const args = str.split(':') - - // number - const nums = args.map(function (arg) { - // use Number and not parseFloat as Number returns NaN on invalid garbage in the string - return Number(arg) - }) - - const invalid = nums.some(function (num) { - return isNaN(num) - }) - if (invalid) { - return null - } - - switch (nums.length) { - case 2: - return { - start: nums[0], - end: nums[1], - step: 1 - } - - case 3: - return { - start: nums[0], - end: nums[2], - step: nums[1] - } - - default: - return null - } - } }) diff --git a/src/function/matrix/squeeze.js b/src/function/matrix/squeeze.js index 9361b24ca4..e6a0220c29 100644 --- a/src/function/matrix/squeeze.js +++ b/src/function/matrix/squeeze.js @@ -18,15 +18,20 @@ export const createSqueeze = /* #__PURE__ */ factory(name, dependencies, ({ type * math.squeeze([3]) // returns 3 * math.squeeze([[3]]) // returns 3 * - * const A = math.zeros(3, 1) // returns [[0], [0], [0]] (size 3x1) - * math.squeeze(A) // returns [0, 0, 0] (size 3) - * - * const B = math.zeros(1, 3) // returns [[0, 0, 0]] (size 1x3) - * math.squeeze(B) // returns [0, 0, 0] (size 3) - * - * // only inner and outer dimensions are removed - * const C = math.zeros(2, 1, 3) // returns [[[0, 0, 0]], [[0, 0, 0]]] (size 2x1x3) - * math.squeeze(C) // returns [[[0, 0, 0]], [[0, 0, 0]]] (size 2x1x3) + * // Squeezes size 3x1 to size 3: + * const A = math.zeros(3, 1) + * A // Matrix [[0], [0], [0]] ... + * math.squeeze(A) // Matrix [0, 0, 0] + * + * // Also squeezes size 1x3 to 3: + * const B = math.zeros(1, 3) + * B // Matrix [[0, 0, 0]] ... + * math.squeeze(B) // Matrix [0, 0, 0] + * + * // only inner and outer dimensions are removed: + * const C = math.zeros(2, 1, 3) + * C // Matrix [[[0, 0, 0]], [[0, 0, 0]]] ... + * math.squeeze(C) // Matrix [[[0, 0, 0]], [[0, 0, 0]]] * * See also: * diff --git a/src/function/matrix/subset.js b/src/function/matrix/subset.js index 8ca59e0523..3de8e3410a 100644 --- a/src/function/matrix/subset.js +++ b/src/function/matrix/subset.js @@ -6,12 +6,35 @@ import { DimensionError } from '../../error/DimensionError.js' import { factory } from '../../utils/factory.js' const name = 'subset' -const dependencies = ['typed', 'matrix', 'zeros', 'add'] +export const dependencies = ['typed', 'matrix', 'zeros', 'add', 'index', 'size'] -export const createSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, zeros, add }) => { +export const createSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, zeros, add, index, size }) => { /** * Get or set a subset of a matrix or string. * + * The second argument should be a specification of the desired subset of + * the first argument. Therefore, the second argument is typically an Index + * object produced by the `index` function, which see (in short, for each + * dimension of the matrix, the Index specifies one position or a list or + * range of positions to include in the subset). + * + * For convenience, the second argument may be simply a number n, in which + * case the subset is the entire section of one dimension lower than the + * given matrix, at position n. In other words, it corresponds to the + * entry of a vector at position n, or the row of a 2D matrix at position + * n, etc. + * + * Furthermore, it can also be an array of appropriate arguments to the + * `index` function, in which case it will be passed to the `index` function + * for you. Beware, though: in the case of a 1d vector v, + * `math.subset(v, [2, 3])` will not therefore return the elements at + * positions 2 and 3, because passing those arguments to `index` would + * attempt to index the first dimension of v by 2 and its nonexistent second + * dimension by 3. You can call `math.subset(v, [[2, 3]])` to obtain the + * elements at positions 2 and 3, because now the inner `[2, 3]` will be + * interpreted as the list of positions with which to index into the first + * dimension. + * * Syntax: * math.subset(value, index) // retrieve a subset * math.subset(value, index, replacement [, defaultValue]) // replace a subset @@ -20,15 +43,21 @@ export const createSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed * * // get a subset * const d = [[1, 2], [3, 4]] - * math.subset(d, math.index(1, 0)) // returns 3 - * math.subset(d, math.index([0, 1], [1])) // returns [[2], [4]] - * math.subset(d, math.index([false, true], [0])) // returns [[3]] + * math.subset(d, math.index(1, 0)) // returns 3 ... + * math.subset(d, [1, 0]) // returns 3 ... + * math.subset(d, math.index([0, 1], [1])) // Array [[2], [4]] ... + * math.subset(d, [[0, 1], [1]]) // Array [[2], [4]] ... + * math.subset(d, math.index([false, true], [0])) // Array [[3]] ... + * math.subset(d, [[false, true], 0]) // Array [3] ... + * math.subset(d, 1) // Array [3, 4] * * // replace a subset * const e = [] - * const f = math.subset(e, math.index(0, [0, 2]), [5, 6]) // f = [[5, 0, 6]] - * const g = math.subset(f, math.index(1, 1), 7, 0) // g = [[5, 0, 6], [0, 7, 0]] - * math.subset(g, math.index([false, true], 1), 8) // returns [[5, 0, 6], [0, 8, 0]] + * const f = math.subset(e, math.index(0, [0, 2]), [5, 6]) + * f // Array [[5, 0, 6]] ... + * const g = math.subset(f, math.index(1, 1), 7, 0) + * g // Array [[5, 0, 6], [0, 7, 0]] ... + * math.subset(g, math.index([false, true], 1), 8) // Array [[5, 0, 6], [0, 8, 0]] * * // get submatrix using ranges * const M = [ @@ -36,7 +65,7 @@ export const createSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed * [4, 5, 6], * [7, 8, 9] * ] - * math.subset(M, math.index(math.range(0,2), math.range(0,3))) // [[1, 2, 3], [4, 5, 6]] + * math.subset(M, math.index(math.range(0,2), math.range(0,3))) // Array [[1, 2, 3], [4, 5, 6]] * * See also: * @@ -75,6 +104,23 @@ export const createSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed 'string, Index': _getSubstring, + // Allow single number index to get layer: + 'Matrix, number': function (M, position) { + return M.layer(position) + }, + + 'Array, number': function (A, position) { + return A[position] + }, + + 'string, number': function (s, position) { + return s.charAt(position) + }, + + // Otherwise pass second array argument to index function for convenience + 'Matrix | Array | Object | string, Array': typed.referToSelf( + self => (v, i) => self(v, index(...i))), + // set subset 'Matrix, Index, any, any': function (value, index, replacement, defaultValue) { if (isEmptyIndex(index)) { return value } @@ -89,19 +135,31 @@ export const createSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed } }), - 'Array, Index, any': typed.referTo('Matrix, Index, any, any', function (subsetRef) { - return function (value, index, replacement) { - return subsetRef(matrix(value), index, replacement, undefined).valueOf() - } - }), - - 'Matrix, Index, any': typed.referTo('Matrix, Index, any, any', function (subsetRef) { - return function (value, index, replacement) { return subsetRef(value, index, replacement, undefined) } - }), - 'string, Index, string': _setSubstring, 'string, Index, string, string': _setSubstring, - 'Object, Index, any': _setObjectProperty + 'Object, Index, any': _setObjectProperty, + + // fourth argument defaults to undefined: + 'Matrix | Array, Index | Array | number, any': typed.referToSelf( + self => (v, pos, rep) => self(v, pos, rep, undefined) + ), + + // Allow 2nd index to be a number: + 'Matrix | Array | Object | string, number, any, any': typed.referToSelf( + self => (v, pos, rep, def) => { + const ix = [pos] + let wildcards = size(v).length + while (--wildcards > 0) ix.push(':') + return self(v, index(...ix), rep, def) + }), + + 'string, number, string': typed.referTo( + 'string, Index, string', sis => (s, n, rep) => sis(s, index(n), rep)), + + // Or allow 2nd argument to be an array of arguments to index + 'Matrix | Array | Object | string, Array, any, any': typed.referToSelf( + self => (v, ixes, rep, def) => self(v, index(...ixes), rep, def) + ) }) /** diff --git a/src/function/matrix/zeros.js b/src/function/matrix/zeros.js index 95f5d79129..11e78bc92f 100644 --- a/src/function/matrix/zeros.js +++ b/src/function/matrix/zeros.js @@ -4,9 +4,13 @@ import { resize } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'zeros' -const dependencies = ['typed', 'config', 'matrix', 'BigNumber'] +const dependencies = [ + 'typed', 'config', 'DenseMatrix', 'SparseMatrix', 'Range', 'BigNumber' +] -export const createZeros = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, matrix, BigNumber }) => { +export const createZeros = /* #__PURE__ */ factory(name, dependencies, ({ + typed, config, DenseMatrix, SparseMatrix, Range, BigNumber +}) => { /** * Create a matrix filled with zeros. The created matrix can have one or * multiple dimensions. @@ -26,6 +30,7 @@ export const createZeros = /* #__PURE__ */ factory(name, dependencies, ({ typed, * 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] + * math.zeros(3, 'range') // returns new math.Range({start: 0, step: 0, length: 3}) * * const A = [[1, 2, 3], [4, 5, 6]] * math.zeros(math.size(A)) // returns [[0, 0, 0], [0, 0, 0]] @@ -86,7 +91,24 @@ export const createZeros = /* #__PURE__ */ factory(name, dependencies, ({ typed, if (format) { // return a matrix - const m = matrix(format) + if (format === 'range') { + if (size.length === 0) { + return new Range({ start: defaultValue, length: 0 }) + } + if (size.length === 1) { + return new Range({ + start: defaultValue, + length: size[0], + step: defaultValue + }) + } + return new Range({ + start: _zeros(size.slice(1), format), + length: size[0], + step: defaultValue + }) + } + const m = format === 'sparse' ? new SparseMatrix() : new DenseMatrix() if (size.length > 0) { return m.resize(size, defaultValue) } diff --git a/src/function/probability/factorial.js b/src/function/probability/factorial.js index 081f5e702d..8cd425f9a4 100644 --- a/src/function/probability/factorial.js +++ b/src/function/probability/factorial.js @@ -1,49 +1,128 @@ import { deepMap } from '../../utils/collection.js' import { factory } from '../../utils/factory.js' +import { product } from '../../utils/product.js' +import { factorialNumber } from '../../plain/number/index.js' const name = 'factorial' -const dependencies = ['typed', 'gamma'] +const dependencies = ['typed', 'isInteger', '?BigNumber', 'equalScalar'] + +export const createFactorial = /* #__PURE__ */ factory(name, dependencies, ({ + typed, isInteger, BigNumber, equalScalar +}) => { + const bigMemo = BigNumber + ? [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800] + .map(n => new BigNumber(n)) + : null + // Fortunately, the largest argument the factorial of which + // fits in a BigNumber is less than MAX_SAFE_INTEGER + // This value was derived, with gratitude, via + // [Hypercalc](https://mrob.com/pub/comp/hypercalc/hypercalc-javascript.html) + const MAX_BIGNUMBER_FACTORIAL = 626622622402120 -export const createFactorial = /* #__PURE__ */ factory(name, dependencies, ({ typed, gamma }) => { /** - * Compute the factorial of a value + * Compute the factorial of a value. + * + * The factorial of _n_ (which you can write as `n!` in the expression + * parser) is the product of all of the positive integers less than + * or equal to _n_, with a special case that `0!` is 1. As such, factorial + * only supports a nonnegative integer value (of any datatype) as its + * argument, and returns a result of the same datatype, except that the + * factorial of an (integer) Fraction is returned as a bigint and of a + * (real, integer) Complex number is returned as a plain number. * - * Factorial only supports an integer value as argument. * For matrices, the function is evaluated element wise. * + * **Variant factorials** + * + * For computing variations on the factorial, such as the "_n_ th rising + * factorial of _x_" equal to _x·(x+1)·...·(x+n-1)_, or the "_n_ th falling + * factorial of _x_" equal to _x·(x-1)·...·(x-(n-1))_, or the "double + * factorial of _n_" equal to _n·(n-2)·..._ (ending at 2 if _n_ is even or + * 1 if _n_ is odd), mathjs recommends you use the `prod` function on the + * arithmetic sequence of factors, generated with the `range` function. + * Explicitly, we have that + * + * - nth rising factorial of x is `math.prod(math.range(x, x + n))`, or in + * the expression parser (which has different conventions for specifying + * ranges) `prod(x:x+n-1)` + * - nth falling factorial of x is `math.prod(math.range(x, x - n, -1))` or + * in the expression parser either `prod(x:-1:x-n+1)` or `prod(x-n+1:x)`. + * - double factorial of n is `math.prod(math.range(n, 1, -2))` or in the + * expression parser `prod(n:-2:1)`. + * + * Of course, you can for example use `prod(1:n)` in the expression parser + * for `n!` but the `factorial` function has a specialized implementation + * that is approximately 33% faster for large, high-precision values. + * * Syntax: * * math.factorial(n) * * Examples: * - * math.factorial(5) // returns 120 - * math.factorial(3) // returns 6 + * math.factorial(5) // returns 120 + * const big21 = math.bignumber(21) + * math.factorial(big21) // returns BigNumber 51090942171709440000 * * See also: * - * combinations, combinationsWithRep, gamma, permutations + * combinations, combinationsWithRep, gamma, permutations, prod, range * - * @param {number | BigNumber | Array | Matrix} n An integer number - * @return {number | BigNumber | Array | Matrix} The factorial of `n` + * @param {number | bigint | Fraction | Complex | BigNumber | Array | Matrix} n An integer number or matrix thereof + * @return {number | bigint | BigNumber | Array | Matrix} + * The (possibly elementwise) factorial of `n` */ return typed(name, { - number: function (n) { - if (n < 0) { - throw new Error('Value must be non-negative') + number: factorialNumber, + bigint: function (b) { + if (b < 0n) { + throw new RangeError('factorial requires a nonnegative argument.') } - - return gamma(n + 1) + return product(1n, b) + }, + Fraction: function (f) { + if (f.s < 0 || !isInteger(f)) { + throw new RangeError('factorial requires nonnegative integer argument.') + } + return product(1n, f.n) + }, + Complex: function (z) { + if (!equalScalar(z.re, z.re + z.im)) { + throw new RangeError('factorial requires nonnegative integer argument.') + } + return factorialNumber(z.re) }, - BigNumber: function (n) { - if (n.isNegative()) { - throw new Error('Value must be non-negative') + // When n overflows `number`, n! will overflow BigNumber + if (n.toNumber() === Infinity) return new BigNumber(Infinity) + if (n.isNegative() || !isInteger(n)) { + throw new RangeError('factorial requires nonnegative integer argument.') } - - return gamma(n.plus(1)) + return bigFactorial(n) }, 'Array | Matrix': typed.referToSelf(self => n => deepMap(n, self)) }) + + /* NB: only call this with a nonnegative integer */ + function bigFactorial (n) { + if (n.lessThan(11)) return bigMemo[n.toNumber()] + if (n.greaterThan(MAX_BIGNUMBER_FACTORIAL)) return new BigNumber(Infinity) + if (n.modulo(2).isPositive()) return n.times(bigFactorial(n.minus(1))) + + const Big = BigNumber.clone({ + precision: BigNumber.precision + (Math.log(n.toNumber()) | 0) + }) + + let p = n.toNumber() + let prod = new Big(n) + let sum = n // can overflow MAX_SAFE_INTEGER for p < MAX_BIGNUMBER_FACTORIAL + while (p > 2) { + p -= 2 + sum = sum.plus(p) + prod = prod.times(sum) + } + + return new BigNumber(prod.toPrecision(BigNumber.precision)) + } }) diff --git a/src/function/probability/gamma.js b/src/function/probability/gamma.js index 2fb37729b6..54445fbd02 100644 --- a/src/function/probability/gamma.js +++ b/src/function/probability/gamma.js @@ -2,9 +2,15 @@ import { factory } from '../../utils/factory.js' import { gammaG, gammaNumber, gammaP } from '../../plain/number/index.js' const name = 'gamma' -const dependencies = ['typed', 'config', 'multiplyScalar', 'pow', 'BigNumber', 'Complex'] - -export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, multiplyScalar, pow, BigNumber, Complex }) => { +const dependencies = [ + 'typed', 'config', 'BigNumber', 'Complex', + 'equalScalar', 'multiplyScalar', 'pow', 'factorial' +] + +export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({ + typed, config, BigNumber, Complex, + equalScalar, multiplyScalar, pow, factorial +}) => { /** * Compute the gamma function of a value using Lanczos approximation for * small values, and an extended Stirling approximation for large values. @@ -31,9 +37,8 @@ export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({ typed, */ function gammaComplex (n) { - if (n.im === 0) { - return gammaNumber(n.re) - } + // Handle the "essentially real" case + if (equalScalar(n.re, n.re + n.im)) return gammaNumber(n.re) // Lanczos approximation doesn't work well with real part lower than 0.5 // So reflection formula is required @@ -77,9 +82,8 @@ export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({ typed, Complex: gammaComplex, BigNumber: function (n) { if (n.isInteger()) { - return (n.isNegative() || n.isZero()) - ? new BigNumber(Infinity) - : bigFactorial(n.minus(1)) + if (n.isNegative() || n.isZero()) return new BigNumber(Infinity) + return factorial(n.minus(1)) } if (!n.isFinite()) { @@ -89,34 +93,4 @@ export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({ typed, throw new Error('Integer BigNumber expected') } }) - - /** - * Calculate factorial for a BigNumber - * @param {BigNumber} n - * @returns {BigNumber} Returns the factorial of n - */ - function bigFactorial (n) { - if (n < 8) { - return new BigNumber([1, 1, 2, 6, 24, 120, 720, 5040][n]) - } - - const precision = config.precision + (Math.log(n.toNumber()) | 0) - const Big = BigNumber.clone({ precision }) - - if (n % 2 === 1) { - return n.times(bigFactorial(new BigNumber(n - 1))) - } - - let p = n - let prod = new Big(n) - let sum = n.toNumber() - - while (p > 2) { - p -= 2 - sum += p - prod = prod.times(sum) - } - - return new BigNumber(prod.toPrecision(BigNumber.precision)) - } }) diff --git a/src/function/relational/compare.js b/src/function/relational/compare.js index 39886c3920..968df6f395 100644 --- a/src/function/relational/compare.js +++ b/src/function/relational/compare.js @@ -11,19 +11,17 @@ const name = 'compare' const dependencies = [ 'typed', 'config', - 'matrix', 'equalScalar', 'BigNumber', 'Fraction', - 'DenseMatrix', - 'concat' + 'DenseMatrix' ] -export const createCompare = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, equalScalar, matrix, BigNumber, Fraction, DenseMatrix, concat }) => { +export const createCompare = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, equalScalar, BigNumber, Fraction, DenseMatrix }) => { const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) const matAlgo05xSfSf = createMatAlgo05xSfSf({ typed, equalScalar }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) const compareUnits = createCompareUnits({ typed }) /** diff --git a/src/function/relational/compareText.js b/src/function/relational/compareText.js index 310b55cd0e..c38d82e14a 100644 --- a/src/function/relational/compareText.js +++ b/src/function/relational/compareText.js @@ -3,16 +3,11 @@ import { factory } from '../../utils/factory.js' import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgorithmSuite.js' const name = 'compareText' -const dependencies = [ - 'typed', - 'matrix', - 'concat' -] - +const dependencies = ['typed', 'DenseMatrix'] _compareText.signature = 'any, any' -export const createCompareText = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, concat }) => { - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) +export const createCompareText = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix }) => { + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Compare two strings lexically. Comparison is case sensitive. diff --git a/src/function/relational/compareUnits.js b/src/function/relational/compareUnits.js index 5b4937d26b..8830428c2b 100644 --- a/src/function/relational/compareUnits.js +++ b/src/function/relational/compareUnits.js @@ -1,12 +1,26 @@ import { factory } from '../../utils/factory.js' export const createCompareUnits = /* #__PURE__ */ factory( - 'compareUnits', ['typed'], ({ typed }) => ({ + 'compareUnits', ['typed'], ({ typed, Unit }) => ({ '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) - }) + }), + 'Unit, number | bigint | BigNumber | Fraction | Complex': typed.referToSelf( + self => (x, y) => { + if (!x.unitless()) { + throw new Error('To compare Unit with pure numeric, must be unitless') + } + return self(x.value, y) + }), + 'number | bigint | BigNumber | Fraction | Complex, Unit': typed.referToSelf( + self => (x, y) => { + if (!y.unitless()) { + throw new Error('To compare Unit with pure numeric, must be unitless') + } + return self(x, y.value) + }) }) ) diff --git a/src/function/relational/deepEqual.js b/src/function/relational/deepEqual.js index f693b8b6e9..ea6540bccb 100644 --- a/src/function/relational/deepEqual.js +++ b/src/function/relational/deepEqual.js @@ -1,5 +1,4 @@ import { factory } from '../../utils/factory.js' - const name = 'deepEqual' const dependencies = [ 'typed', @@ -37,9 +36,11 @@ export const createDeepEqual = /* #__PURE__ */ factory(name, dependencies, ({ ty * Returns true when the input matrices have the same size and each of their elements is equal. */ return typed(name, { - 'any, any': function (x, y) { - return _deepEqual(x.valueOf(), y.valueOf()) - } + // Don't want to execute .valueOf() on a unit, as it returns a string, + // messing up the equality test + 'Unit, any': (u, x) => equal(u, x), + 'any, Unit': (x, u) => equal(x, u), + 'any, any': (x, y) => _deepEqual(x.valueOf(), y.valueOf()) }) /** diff --git a/src/function/relational/equal.js b/src/function/relational/equal.js index a4e1031b68..66bc660a4d 100644 --- a/src/function/relational/equal.js +++ b/src/function/relational/equal.js @@ -7,18 +7,16 @@ import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgori const name = 'equal' const dependencies = [ 'typed', - 'matrix', 'equalScalar', 'DenseMatrix', 'SparseMatrix' ] -export const createEqual = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, DenseMatrix, concat, SparseMatrix }) => { +export const createEqual = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar, DenseMatrix, concat, SparseMatrix }) => { const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) const matAlgo07xSSf = createMatAlgo07xSSf({ typed, SparseMatrix }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix }) - + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Test whether two values are equal. * diff --git a/src/function/relational/equalScalar.js b/src/function/relational/equalScalar.js index eea6881e4d..835c223cdb 100644 --- a/src/function/relational/equalScalar.js +++ b/src/function/relational/equalScalar.js @@ -7,14 +7,14 @@ import { createCompareUnits } from './compareUnits.js' const name = 'equalScalar' const dependencies = ['typed', 'config'] -export const createEqualScalar = /* #__PURE__ */ factory(name, dependencies, ({ typed, config }) => { +export const createEqualScalar = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, Unit }) => { const compareUnits = createCompareUnits({ typed }) /** * Test whether two scalar values are nearly equal. * * @param {number | BigNumber | bigint | Fraction | boolean | Complex | Unit} x First value to compare - * @param {number | BigNumber | bigint | Fraction | boolean | Complex} y Second value to compare + * @param {number | BigNumber | bigint | Fraction | boolean | Complex | Unit} y Second value to compare * @return {boolean} Returns true when the compared values are equal, else returns false * @private */ diff --git a/src/function/relational/larger.js b/src/function/relational/larger.js index 8507d21511..131d054e25 100644 --- a/src/function/relational/larger.js +++ b/src/function/relational/larger.js @@ -12,9 +12,7 @@ const dependencies = [ 'typed', 'config', 'bignumber', - 'matrix', 'DenseMatrix', - 'concat', 'SparseMatrix' ] @@ -22,7 +20,7 @@ export const createLarger = /* #__PURE__ */ factory(name, dependencies, ({ typed const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) const matAlgo07xSSf = createMatAlgo07xSSf({ typed, SparseMatrix }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) const compareUnits = createCompareUnits({ typed }) /** diff --git a/src/function/relational/largerEq.js b/src/function/relational/largerEq.js index 6760df7f6c..78b353dfd7 100644 --- a/src/function/relational/largerEq.js +++ b/src/function/relational/largerEq.js @@ -11,9 +11,7 @@ const name = 'largerEq' const dependencies = [ 'typed', 'config', - 'matrix', 'DenseMatrix', - 'concat', 'SparseMatrix' ] @@ -21,7 +19,7 @@ export const createLargerEq = /* #__PURE__ */ factory(name, dependencies, ({ typ const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) const matAlgo07xSSf = createMatAlgo07xSSf({ typed, SparseMatrix }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) const compareUnits = createCompareUnits({ typed }) /** diff --git a/src/function/relational/smaller.js b/src/function/relational/smaller.js index 87807f0240..912dd95db3 100644 --- a/src/function/relational/smaller.js +++ b/src/function/relational/smaller.js @@ -12,17 +12,16 @@ const dependencies = [ 'typed', 'config', 'bignumber', - 'matrix', 'DenseMatrix', 'concat', 'SparseMatrix' ] -export const createSmaller = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, bignumber, matrix, DenseMatrix, concat, SparseMatrix }) => { +export const createSmaller = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, bignumber, DenseMatrix, concat, SparseMatrix }) => { const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) const matAlgo07xSSf = createMatAlgo07xSSf({ typed, SparseMatrix }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) const compareUnits = createCompareUnits({ typed }) /** diff --git a/src/function/relational/smallerEq.js b/src/function/relational/smallerEq.js index b314418a4c..272ed22303 100644 --- a/src/function/relational/smallerEq.js +++ b/src/function/relational/smallerEq.js @@ -11,7 +11,6 @@ const name = 'smallerEq' const dependencies = [ 'typed', 'config', - 'matrix', 'DenseMatrix', 'concat', 'SparseMatrix' @@ -21,7 +20,7 @@ export const createSmallerEq = /* #__PURE__ */ factory(name, dependencies, ({ ty const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) const matAlgo07xSSf = createMatAlgo07xSSf({ typed, SparseMatrix }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) const compareUnits = createCompareUnits({ typed }) /** diff --git a/src/function/relational/unequal.js b/src/function/relational/unequal.js index 1accaf846b..b89345d3b2 100644 --- a/src/function/relational/unequal.js +++ b/src/function/relational/unequal.js @@ -9,9 +9,7 @@ const dependencies = [ 'typed', 'config', 'equalScalar', - 'matrix', 'DenseMatrix', - 'concat', 'SparseMatrix' ] @@ -19,7 +17,7 @@ export const createUnequal = /* #__PURE__ */ factory(name, dependencies, ({ type const matAlgo03xDSf = createMatAlgo03xDSf({ typed }) const matAlgo07xSSf = createMatAlgo07xSSf({ typed, SparseMatrix }) const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix }) - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Test whether two values are unequal. diff --git a/src/function/set/setCartesian.js b/src/function/set/setCartesian.js index 1f259e7a5b..b901284370 100644 --- a/src/function/set/setCartesian.js +++ b/src/function/set/setCartesian.js @@ -2,9 +2,9 @@ import { flatten } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'setCartesian' -const dependencies = ['typed', 'size', 'subset', 'compareNatural', 'Index', 'DenseMatrix'] +const dependencies = ['typed', 'size', 'compareNatural'] -export const createSetCartesian = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural, Index, DenseMatrix }) => { +export const createSetCartesian = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, compareNatural }) => { /** * Create the cartesian product of two (multi)sets. * Multi-dimension arrays will be converted to single-dimension arrays @@ -30,10 +30,10 @@ export const createSetCartesian = /* #__PURE__ */ factory(name, dependencies, ({ return typed(name, { 'Array | Matrix, Array | Matrix': function (a1, a2) { let result = [] - - if (subset(size(a1), new Index(0)) !== 0 && subset(size(a2), new Index(0)) !== 0) { // if any of them is empty, return empty - const b1 = flatten(Array.isArray(a1) ? a1 : a1.toArray()).sort(compareNatural) - const b2 = flatten(Array.isArray(a2) ? a2 : a2.toArray()).sort(compareNatural) + // if either is empty, return empty + if (size(a1)[0] !== 0 && size(a2)[0] !== 0) { + const b1 = flatten(a1.valueOf()).sort(compareNatural) + const b2 = flatten(a2.valueOf()).sort(compareNatural) result = [] for (let i = 0; i < b1.length; i++) { for (let j = 0; j < b2.length; j++) { @@ -42,11 +42,11 @@ export const createSetCartesian = /* #__PURE__ */ factory(name, dependencies, ({ } } // return an array, if both inputs were arrays - if (Array.isArray(a1) && Array.isArray(a2)) { - return result + if (Array.isArray(a1)) { + if (Array.isArray(a2)) return result + return a2.create(result) } - // return a matrix otherwise - return new DenseMatrix(result) + return a1.create(result) } }) }) diff --git a/src/function/set/setDifference.js b/src/function/set/setDifference.js index 872c369e95..9dbde4cc50 100644 --- a/src/function/set/setDifference.js +++ b/src/function/set/setDifference.js @@ -2,9 +2,9 @@ import { flatten, generalize, identify } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'setDifference' -const dependencies = ['typed', 'size', 'subset', 'compareNatural', 'Index', 'DenseMatrix'] +const dependencies = ['typed', 'size', 'compareNatural'] -export const createSetDifference = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural, Index, DenseMatrix }) => { +export const createSetDifference = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, compareNatural }) => { /** * Create the difference of two (multi)sets: every element of set1, that is not the element of set2. * Multi-dimension arrays will be converted to single-dimension arrays before the operation. @@ -28,15 +28,14 @@ export const createSetDifference = /* #__PURE__ */ factory(name, dependencies, ( */ return typed(name, { 'Array | Matrix, Array | Matrix': function (a1, a2) { - let result - if (subset(size(a1), new Index(0)) === 0) { // empty-anything=empty - result = [] - } else if (subset(size(a2), new Index(0)) === 0) { // anything-empty=anything - return flatten(a1.toArray()) - } else { - const b1 = identify(flatten(Array.isArray(a1) ? a1 : a1.toArray()).sort(compareNatural)) - const b2 = identify(flatten(Array.isArray(a2) ? a2 : a2.toArray()).sort(compareNatural)) - result = [] + let result = [] + // empty - anything = empty + if (size(a1)[0] !== 0) { + if (size(a2)[0] === 0) { // anything - empty = anything + return flatten(a1.valueOf()) + } + const b1 = identify(flatten(a1.valueOf()).sort(compareNatural)) + const b2 = identify(flatten(a2.valueOf()).sort(compareNatural)) let inb2 for (let i = 0; i < b1.length; i++) { inb2 = false @@ -51,12 +50,13 @@ export const createSetDifference = /* #__PURE__ */ factory(name, dependencies, ( } } } + result = generalize(result) // remove the identifiers // return an array, if both inputs were arrays - if (Array.isArray(a1) && Array.isArray(a2)) { - return generalize(result) + if (Array.isArray(a1)) { + if (Array.isArray(a2)) return result + return a2.create(result) } - // return a matrix otherwise - return new DenseMatrix(generalize(result)) + return a1.create(result) } }) }) diff --git a/src/function/set/setDistinct.js b/src/function/set/setDistinct.js index 231ff76554..f9cbb6b9a5 100644 --- a/src/function/set/setDistinct.js +++ b/src/function/set/setDistinct.js @@ -2,9 +2,9 @@ import { flatten } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'setDistinct' -const dependencies = ['typed', 'size', 'subset', 'compareNatural', 'Index', 'DenseMatrix'] +const dependencies = ['typed', 'size', 'compareNatural', 'Index', 'DenseMatrix'] -export const createSetDistinct = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural, Index, DenseMatrix }) => { +export const createSetDistinct = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, compareNatural }) => { /** * Collect the distinct elements of a multiset. * A multi-dimension array will be converted to a single-dimension array before the operation. @@ -27,12 +27,10 @@ export const createSetDistinct = /* #__PURE__ */ factory(name, dependencies, ({ */ return typed(name, { 'Array | Matrix': function (a) { - let result - if (subset(size(a), new Index(0)) === 0) { // if empty, return empty - result = [] - } else { - const b = flatten(Array.isArray(a) ? a : a.toArray()) - result = [] + const result = [] + // if empty, return empty + if (size(a)[0] !== 0) { + const b = flatten(a.valueOf()) result.push(b[0]) for (let i = 1; i < b.length; i++) { if (!result.some(item => compareNatural(b[i], item) === 0)) { @@ -45,7 +43,7 @@ export const createSetDistinct = /* #__PURE__ */ factory(name, dependencies, ({ return result } // return a matrix otherwise - return new DenseMatrix(result) + return a.create(result) } }) }) diff --git a/src/function/set/setIntersect.js b/src/function/set/setIntersect.js index 4f2845e507..1db76000dd 100644 --- a/src/function/set/setIntersect.js +++ b/src/function/set/setIntersect.js @@ -2,9 +2,9 @@ import { flatten, generalize, identify } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'setIntersect' -const dependencies = ['typed', 'size', 'subset', 'compareNatural', 'Index', 'DenseMatrix'] +const dependencies = ['typed', 'size', 'compareNatural'] -export const createSetIntersect = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural, Index, DenseMatrix }) => { +export const createSetIntersect = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, compareNatural }) => { /** * Create the intersection of two (multi)sets. * Multi-dimension arrays will be converted to single-dimension arrays before the operation. @@ -28,13 +28,11 @@ export const createSetIntersect = /* #__PURE__ */ factory(name, dependencies, ({ */ return typed(name, { 'Array | Matrix, Array | Matrix': function (a1, a2) { - let result - if (subset(size(a1), new Index(0)) === 0 || subset(size(a2), new Index(0)) === 0) { // of any of them is empty, return empty - result = [] - } else { - const b1 = identify(flatten(Array.isArray(a1) ? a1 : a1.toArray()).sort(compareNatural)) - const b2 = identify(flatten(Array.isArray(a2) ? a2 : a2.toArray()).sort(compareNatural)) - result = [] + let result = [] + // if both are nonempty, we must compute the intersection + if (size(a1)[0] !== 0 && size(a2)[0] !== 0) { + const b1 = identify(flatten(a1.valueOf()).sort(compareNatural)) + const b2 = identify(flatten(a2.valueOf()).sort(compareNatural)) for (let i = 0; i < b1.length; i++) { for (let j = 0; j < b2.length; j++) { if (compareNatural(b1[i].value, b2[j].value) === 0 && b1[i].identifier === b2[j].identifier) { // the identifier is always a decimal int @@ -44,12 +42,13 @@ export const createSetIntersect = /* #__PURE__ */ factory(name, dependencies, ({ } } } + result = generalize(result) // remove the identifiers // return an array, if both inputs were arrays - if (Array.isArray(a1) && Array.isArray(a2)) { - return generalize(result) + if (Array.isArray(a1)) { + if (Array.isArray(a2)) return result + return a2.create(result) } - // return a matrix otherwise - return new DenseMatrix(generalize(result)) + return a1.create(result) } }) }) diff --git a/src/function/set/setIsSubset.js b/src/function/set/setIsSubset.js index d2a8c72217..20e5fa912c 100644 --- a/src/function/set/setIsSubset.js +++ b/src/function/set/setIsSubset.js @@ -2,9 +2,9 @@ import { flatten, identify } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'setIsSubset' -const dependencies = ['typed', 'size', 'subset', 'compareNatural', 'Index'] +const dependencies = ['typed', 'size', 'compareNatural'] -export const createSetIsSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural, Index }) => { +export const createSetIsSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, compareNatural }) => { /** * Check whether a (multi)set is a subset of another (multi)set. (Every element of set1 is the element of set2.) * Multi-dimension arrays will be converted to single-dimension arrays before the operation. @@ -28,9 +28,10 @@ export const createSetIsSubset = /* #__PURE__ */ factory(name, dependencies, ({ */ return typed(name, { 'Array | Matrix, Array | Matrix': function (a1, a2) { - if (subset(size(a1), new Index(0)) === 0) { // empty is a subset of anything + if (size(a1)[0] === 0) { // empty is a subset of anything return true - } else if (subset(size(a2), new Index(0)) === 0) { // anything is not a subset of empty + } + if (size(a2)[0] === 0) { // anything nonempty is not a subset of empty return false } const b1 = identify(flatten(Array.isArray(a1) ? a1 : a1.toArray()).sort(compareNatural)) diff --git a/src/function/set/setMultiplicity.js b/src/function/set/setMultiplicity.js index 7935ac210e..af012c1600 100644 --- a/src/function/set/setMultiplicity.js +++ b/src/function/set/setMultiplicity.js @@ -2,9 +2,9 @@ import { flatten } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'setMultiplicity' -const dependencies = ['typed', 'size', 'subset', 'compareNatural', 'Index'] +const dependencies = ['typed', 'size', 'compareNatural'] -export const createSetMultiplicity = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural, Index }) => { +export const createSetMultiplicity = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, compareNatural }) => { /** * Count the multiplicity of an element in a multiset. * A multi-dimension array will be converted to a single-dimension array before the operation. @@ -28,10 +28,10 @@ export const createSetMultiplicity = /* #__PURE__ */ factory(name, dependencies, */ return typed(name, { 'number | BigNumber | Fraction | Complex, Array | Matrix': function (e, a) { - if (subset(size(a), new Index(0)) === 0) { // if empty, return 0 + if (size(a)[0] === 0) { // if empty, return 0 return 0 } - const b = flatten(Array.isArray(a) ? a : a.toArray()) + const b = flatten(a.valueOf()) let count = 0 for (let i = 0; i < b.length; i++) { if (compareNatural(b[i], e) === 0) { diff --git a/src/function/set/setPowerset.js b/src/function/set/setPowerset.js index 9737854e0a..5201ac54e9 100644 --- a/src/function/set/setPowerset.js +++ b/src/function/set/setPowerset.js @@ -2,12 +2,14 @@ import { flatten } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'setPowerset' -const dependencies = ['typed', 'size', 'subset', 'compareNatural', 'Index'] +const dependencies = ['typed', 'size', 'compareNatural'] -export const createSetPowerset = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural, Index }) => { +export const createSetPowerset = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, compareNatural }) => { /** * Create the powerset of a (multi)set. (The powerset contains very possible subsets of a (multi)set.) * A multi-dimension array will be converted to a single-dimension array before the operation. + * Note this function always returns an Array, regardless of the type + * of the input collection representing the set. * * Syntax: * @@ -26,10 +28,10 @@ export const createSetPowerset = /* #__PURE__ */ factory(name, dependencies, ({ */ return typed(name, { 'Array | Matrix': function (a) { - if (subset(size(a), new Index(0)) === 0) { // if empty, return empty + if (size(a)[0] === 0) { // if empty, return empty return [] } - const b = flatten(Array.isArray(a) ? a : a.toArray()).sort(compareNatural) + const b = flatten(a.valueOf()).sort(compareNatural) const result = [] let number = 0 while (number.toString(2).length <= b.length) { diff --git a/src/function/set/setSymDifference.js b/src/function/set/setSymDifference.js index 812cfbc586..1eb88d2ebd 100644 --- a/src/function/set/setSymDifference.js +++ b/src/function/set/setSymDifference.js @@ -2,9 +2,9 @@ import { flatten } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'setSymDifference' -const dependencies = ['typed', 'size', 'concat', 'subset', 'setDifference', 'Index'] +const dependencies = ['typed', 'size', 'concat', 'setDifference'] -export const createSetSymDifference = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, concat, subset, setDifference, Index }) => { +export const createSetSymDifference = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, concat, setDifference }) => { /** * Create the symmetric difference of two (multi)sets. * Multi-dimension arrays will be converted to single-dimension arrays before the operation. @@ -28,9 +28,10 @@ export const createSetSymDifference = /* #__PURE__ */ factory(name, dependencies */ return typed(name, { 'Array | Matrix, Array | Matrix': function (a1, a2) { - if (subset(size(a1), new Index(0)) === 0) { // if any of them is empty, return the other one + if (size(a1)[0] === 0) { // if either is empty, return the other return flatten(a2) - } else if (subset(size(a2), new Index(0)) === 0) { + } + if (size(a2)[0] === 0) { return flatten(a1) } const b1 = flatten(a1) diff --git a/src/function/set/setUnion.js b/src/function/set/setUnion.js index ce53bea081..db9a2caabf 100644 --- a/src/function/set/setUnion.js +++ b/src/function/set/setUnion.js @@ -2,9 +2,9 @@ import { flatten } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'setUnion' -const dependencies = ['typed', 'size', 'concat', 'subset', 'setIntersect', 'setSymDifference', 'Index'] +const dependencies = ['typed', 'size', 'concat', 'setIntersect', 'setSymDifference'] -export const createSetUnion = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, concat, subset, setIntersect, setSymDifference, Index }) => { +export const createSetUnion = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, concat, setIntersect, setSymDifference }) => { /** * Create the union of two (multi)sets. * Multi-dimension arrays will be converted to single-dimension arrays before the operation. @@ -15,8 +15,8 @@ export const createSetUnion = /* #__PURE__ */ factory(name, dependencies, ({ typ * * Examples: * - * math.setUnion([1, 2, 3, 4], [3, 4, 5, 6]) // returns [1, 2, 3, 4, 5, 6] - * math.setUnion([[1, 2], [3, 4]], [[3, 4], [5, 6]]) // returns [1, 2, 3, 4, 5, 6] + * math.sort(math.setUnion([1, 2, 3, 4], [3, 4, 5, 6])) // returns [1, 2, 3, 4, 5, 6] + * math.sort(math.setUnion([[1, 2], [3, 4]], [[3, 4], [5, 6]])) // returns [1, 2, 3, 4, 5, 6] * * See also: * @@ -28,9 +28,10 @@ export const createSetUnion = /* #__PURE__ */ factory(name, dependencies, ({ typ */ return typed(name, { 'Array | Matrix, Array | Matrix': function (a1, a2) { - if (subset(size(a1), new Index(0)) === 0) { // if any of them is empty, return the other one + if (size(a1)[0] === 0) { // if either is empty, return the other return flatten(a2) - } else if (subset(size(a2), new Index(0)) === 0) { + } + if (size(a2)[0] === 0) { return flatten(a1) } const b1 = flatten(a1) diff --git a/src/function/statistics/prod.js b/src/function/statistics/prod.js index 1a5ba84072..b83ee928e0 100644 --- a/src/function/statistics/prod.js +++ b/src/function/statistics/prod.js @@ -1,16 +1,29 @@ -import { deepForEach } from '../../utils/collection.js' import { factory } from '../../utils/factory.js' +import { isArray, isNumber, isRange } from '../../utils/is.js' import { safeNumberType } from '../../utils/number.js' import { improveErrorMessage } from './utils/improveErrorMessage.js' const name = 'prod' -const dependencies = ['typed', 'config', 'multiplyScalar', 'numeric'] +const dependencies = [ + 'typed', 'config', 'multiplyScalar', 'number', 'numeric', + 'squeeze', 'size', 'subset', 'dotMultiply' +] -export const createProd = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, multiplyScalar, numeric }) => { +// Two tuning constants. For products of at least THRESHOLD terms, we split +// rather than directly multiply. And for products of Ranges of at least +// RANGE_THRESHOLD + 1 terms, we multiply in pairs rather than singly. +const THRESHOLD = 16 +const RANGE_THRESHOLD = 5 + +export const createProd = /* #__PURE__ */ factory(name, dependencies, ({ + typed, config, multiplyScalar, number, numeric, + squeeze, size, subset, dotMultiply +}) => { /** * Compute the product of a matrix or a list with values. - * In case of a multidimensional array or matrix, the sum of all - * elements will be calculated. + * In case of a multidimensional array or matrix, product of all + * elements will be calculated, unless a second integer argument is given + * that specifies the dimension along which to multiply. * * Syntax: * @@ -37,11 +50,7 @@ export const createProd = /* #__PURE__ */ factory(name, dependencies, ({ typed, 'Array | Matrix': _prod, // prod([a, b, c, d, ...], dim) - 'Array | Matrix, number | BigNumber': function (array, dim) { - // TODO: implement prod(A, dim) - throw new Error('prod(A, dim) is not yet supported') - // return reduce(arguments[0], arguments[1], math.prod) - }, + 'Array | Matrix, number | BigNumber': _prodAlongDim, // prod(a, b, c, d, ...) '...': function (args) { @@ -51,30 +60,125 @@ export const createProd = /* #__PURE__ */ factory(name, dependencies, ({ typed, /** * Recursively calculate the product of an n-dimensional array - * @param {Array} array - * @return {number} prod + * @param {Array | Matrix} collection + * @return {scalar} prod * @private */ - function _prod (array) { + function _prod (collection) { + let sz = size(collection) + if (sz.length === 0 || sz.some(dim => dim === 0)) return 1 let prod + try { + if (sz.every(dim => dim === 1)) prod = squeeze(collection) + else { + if (sz.length > 1) { // reduce to 1d + const newColl = [] + for (let pos = 0; pos < sz[0]; ++pos) { + newColl.push(_prod(subset(collection, pos))) + } + collection = newColl + sz = [sz[0]] + } + if (Array.isArray(collection)) { + prod = _prodArray(collection, 0, sz[0] - 1) + } else { + let op = multiplyScalar + const dt = collection.datatype() + if (dt) op = typed.find(op, [dt, dt]) + prod = _prodVector(collection, 0, sz[0] - 1, op) + } + } - deepForEach(array, function (value) { - try { - prod = (prod === undefined) ? value : multiplyScalar(prod, value) - } catch (err) { - throw improveErrorMessage(err, 'prod', value) + if (typeof prod === 'string') { + prod = numeric(prod, safeNumberType(prod, config)) } - }) + } catch (err) { + throw improveErrorMessage(err, 'prod', collection) + } + return prod + } - // make sure returning numeric value: parse a string into a numeric value - if (typeof prod === 'string') { - prod = numeric(prod, safeNumberType(prod, config)) + /* Product of a 1d array arr from index first to index last, inclusive. */ + function _prodArray (arr, first, last) { + if (last - first < THRESHOLD) { + let prod = arr[first] + for (let idx = first + 1; idx <= last; ++idx) { + prod = multiplyScalar(prod, arr[idx]) + } + return prod } + const cutoff = Math.floor((first + last) / 2) + return multiplyScalar( + _prodArray(arr, first, cutoff), + _prodArray(arr, cutoff + 1, last)) + } - if (prod === undefined) { - throw new Error('Cannot calculate prod of an empty array') + /* Product of a 1d vector v from position first to last, using op */ + function _prodVector (v, first, last, op) { + const lenm1 = last - first + if (lenm1 < THRESHOLD) { + if (isRange(v) && lenm1 >= RANGE_THRESHOLD) { + return _prodRange(v, first, last, op) + } + let prod = v.layer(first) + for (let idx = first + 1; idx <= last; ++idx) { + prod = op(prod, v.layer(idx)) + } + return prod } + const cutoff = Math.floor((first + last) / 2) + return op( + _prodVector(v, first, cutoff, op), _prodVector(v, cutoff + 1, last, op)) + } - return prod + /* Product of a 1d Range r from position first to last, using op and the + "multiply in pairs" trick. + */ + function _prodRange (r, first, last, op) { + const a = r.layer(first) + const d = r.step + const m = last - first // m+1 terms + const even = m % 2 + const pairs = even ? (m + 1) / 2 : m / 2 + const b = even ? a : r.plus(a, d) + const k = even ? m : m - 1 // k is always an odd number + // So now the pairs are b*(b+kd), (b+1d)*(b+(k-1)d), (b+2d)*(b+(k-2)d), ... + // up to (b+(pairs-1)d)*(b+(k-pairs+1)d). Their products are all + // (b^2 + kbd + something), where the somethings go (k-1)d^2, 2(k-2)d^2, + // 3(k-3)d, ... with differences (k-3)d^2, (k-5)d^2, (k-7)d^2, ... + let pair = op(b, r.layer(last)) // b^2 + kbd + let prod = pair + const dsq = op(d, d) + let pairIncrement = r.times(k - 1, dsq) + const pairIncDec = r.times(-2, dsq) + for (let j = 1; j < pairs; ++j) { + pair = r.plus(pair, pairIncrement) + pairIncrement = r.plus(pairIncrement, pairIncDec) + prod = op(prod, pair) + } + return even ? prod : op(a, prod) + } + + function _prodAlongDim (collection, dim) { + if (!isNumber(dim)) dim = number(dim) + const sz = size(collection) + if (dim >= sz.length) { + throw new Error( + `There is no dimension ${dim} in collection of size ${sz}.`) + } + if (sz.length === 1) return _prod(collection) + if (dim === 0) { + let result = subset(collection, 0) + for (let i = 1; i < sz[0]; ++i) { + result = dotMultiply(result, subset(collection, i)) + } + return result + } + const data = [] + for (let i = 0; i < sz[0]; ++i) { + data.push(_prodAlongDim(subset(collection, i), dim - 1).valueOf()) + } + if (isArray(collection)) return data + return collection.create(data) } }) diff --git a/src/function/statistics/quantileSeq.js b/src/function/statistics/quantileSeq.js index 4f79af1df3..b4d4bb5b9d 100644 --- a/src/function/statistics/quantileSeq.js +++ b/src/function/statistics/quantileSeq.js @@ -8,12 +8,14 @@ const dependencies = ['typed', '?bignumber', 'add', 'subtract', 'divide', 'multi export const createQuantileSeq = /* #__PURE__ */ factory(name, dependencies, ({ typed, bignumber, add, subtract, divide, multiply, partitionSelect, compare, isInteger, smaller, smallerEq, larger, mapSlices }) => { /** * Compute the prob order quantile of a matrix or a list with values. - * The sequence is sorted and the middle value is returned. + * The sequence is sorted and the quntile value(s) are returned. * Supported types of sequence values are: Number, BigNumber, Unit * Supported types of probability are: Number, BigNumber * * In case of a multidimensional array or matrix, the prob order quantile - * of all elements will be calculated. + * of all elements will be calculated, unless the additional "dimension" + * argument (a number) is specified, in which case the quantiles are + * computed for all slices along that dimension. * * Syntax: * diff --git a/src/function/trigonometry/atan2.js b/src/function/trigonometry/atan2.js index ec0eb544d3..995952e9b8 100644 --- a/src/function/trigonometry/atan2.js +++ b/src/function/trigonometry/atan2.js @@ -9,20 +9,18 @@ import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgori const name = 'atan2' const dependencies = [ 'typed', - 'matrix', 'equalScalar', 'BigNumber', - 'DenseMatrix', - 'concat' + 'DenseMatrix' ] -export const createAtan2 = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, BigNumber, DenseMatrix, concat }) => { +export const createAtan2 = /* #__PURE__ */ factory(name, dependencies, ({ typed, equalScalar, BigNumber, DenseMatrix }) => { 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, concat }) + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Calculate the inverse tangent function with two arguments, y/x. diff --git a/src/function/unit/to.js b/src/function/unit/to.js index e0705a5bd1..4140ab64c6 100644 --- a/src/function/unit/to.js +++ b/src/function/unit/to.js @@ -4,12 +4,11 @@ import { createMatrixAlgorithmSuite } from '../../type/matrix/utils/matrixAlgori const name = 'to' const dependencies = [ 'typed', - 'matrix', - 'concat' + 'DenseMatrix' ] -export const createTo = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, concat }) => { - const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, matrix, concat }) +export const createTo = /* #__PURE__ */ factory(name, dependencies, ({ typed, DenseMatrix }) => { + const matrixAlgorithmSuite = createMatrixAlgorithmSuite({ typed, DenseMatrix }) /** * Change the unit of a value. diff --git a/src/function/utils/isFinite.js b/src/function/utils/isFinite.js index 57dd2b10f0..5bf0ee0354 100644 --- a/src/function/utils/isFinite.js +++ b/src/function/utils/isFinite.js @@ -34,7 +34,7 @@ export const createIsFinite = /* #__PURE__ */ factory(name, dependencies, ({ * isBounded isNumeric, isPositive, isNegative, isNaN * * @param {number | BigNumber | bigint | Complex | Fraction | Unit | Array | Matrix} x Value to be tested - * @return {boolean | Array | Matrix} + * @return {boolean | Array | Matrix} Whether the value, or each entry, is finite. */ return typed(name, { 'Array | Matrix': A => map(A, isBounded), diff --git a/src/plain/number/arithmetic.js b/src/plain/number/arithmetic.js index bb006d4f9a..fcc1025351 100644 --- a/src/plain/number/arithmetic.js +++ b/src/plain/number/arithmetic.js @@ -23,6 +23,14 @@ export function multiplyNumber (a, b) { } multiplyNumber.signature = n2 +/* Multiply every entry of A by the number n */ +export function deepMultiply (n, A) { + return A.map(item => { + if (Array.isArray(item)) return deepMultiply(n, item) + return n * item + }) +} + export function divideNumber (a, b) { return a / b } diff --git a/src/plain/number/probability.js b/src/plain/number/probability.js index fb15bd3f35..6c62168ef4 100644 --- a/src/plain/number/probability.js +++ b/src/plain/number/probability.js @@ -3,19 +3,19 @@ import { isInteger } from '../../utils/number.js' import { product } from '../../utils/product.js' -export function gammaNumber (n) { - let x +export function factorialNumber (n) { + if (n === Infinity) return Infinity + if (n < 0 || !isInteger(n)) { + throw new RangeError('factorial requires nonnegative integer argument.') + } + if (n > 171) return Infinity // will overflow + return product(1, n) +} +export function gammaNumber (n) { if (isInteger(n)) { - if (n <= 0) { - return Number.isFinite(n) ? Infinity : NaN - } - - if (n > 171) { - return Infinity // Will overflow - } - - return product(1, n - 1) + if (n <= 0) return Number.isFinite(n) ? Infinity : NaN + return factorialNumber(n - 1) } if (n < 0.5) { @@ -38,7 +38,7 @@ export function gammaNumber (n) { } --n - x = gammaP[0] + let x = gammaP[0] for (let i = 1; i < gammaP.length; ++i) { x += gammaP[i] / (n + i) } diff --git a/src/type/bignumber/function/bignumber.js b/src/type/bignumber/function/bignumber.js index a38a84ae28..5dd930d9c9 100644 --- a/src/type/bignumber/function/bignumber.js +++ b/src/type/bignumber/function/bignumber.js @@ -15,12 +15,12 @@ export const createBignumber = /* #__PURE__ */ factory(name, dependencies, ({ ty * * Examples: * - * 0.1 + 0.2 // returns number 0.30000000000000004 - * math.bignumber(0.1) + math.bignumber(0.2) // returns BigNumber 0.3 + * math.add(0.1, 0.2) // returns number 0.30000000000000004 + * math.add(math.bignumber(0.1), math.bignumber(0.2)) // BigNumber 0.3 * * * 7.2e500 // returns number Infinity - * math.bignumber('7.2e500') // returns BigNumber 7.2e500 + * math.bignumber('7.2e500') // BigNumber 7.2e500 * * See also: * diff --git a/src/type/complex/function/complex.js b/src/type/complex/function/complex.js index b94327e2b4..7976e6c87e 100644 --- a/src/type/complex/function/complex.js +++ b/src/type/complex/function/complex.js @@ -28,12 +28,16 @@ export const createComplex = /* #__PURE__ */ factory(name, dependencies, ({ type * * Examples: * - * const a = math.complex(3, -4) // a = Complex 3 - 4i - * a.re = 5 // a = Complex 5 - 4i - * const i = a.im // Number -4 - * const b = math.complex('2 + 6i') // Complex 2 + 6i - * const c = math.complex() // Complex 0 + 0i - * const d = math.add(a, b) // Complex 5 + 2i + * const a = math.complex(3, -4) + * a // Complex 3 - 4i ... + * a.re = 5 + * a // Complex 5 - 4i ... + * a.im // number -4 ... + * const b = math.complex('2 + 6i') + * b // Complex 2 + 6i ... + * math.add(a, b) // Complex 7 + 2i + * + * math.complex() // Complex 0 + 0i * * See also: * diff --git a/src/type/fraction/function/fraction.js b/src/type/fraction/function/fraction.js index e1cf0dbf4b..b0982b604c 100644 --- a/src/type/fraction/function/fraction.js +++ b/src/type/fraction/function/fraction.js @@ -30,7 +30,7 @@ export const createFraction = /* #__PURE__ */ factory(name, dependencies, ({ typ * math.fraction(1, 3) // returns Fraction 1/3 * math.fraction('2/3') // returns Fraction 2/3 * math.fraction({n: 2, d: 3}) // returns Fraction 2/3 - * math.fraction([0.2, 0.25, 1.25]) // returns Array [1/5, 1/4, 5/4] + * math.fraction([0.2, 0.25, 1.25]) // [fraction(1,5), fraction(1,4), fraction(5,4)] * math.fraction(4, 5.1) // throws Error: Parameters must be integer * * See also: diff --git a/src/type/matrix/DenseMatrix.js b/src/type/matrix/DenseMatrix.js index 112e0abc33..9633637b25 100644 --- a/src/type/matrix/DenseMatrix.js +++ b/src/type/matrix/DenseMatrix.js @@ -90,6 +90,7 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies * @memberOf DenseMatrix * @return {string} type information; if multiple types are found from the Matrix, it will return "mixed" */ + // TODO: Shouldn't calling this update the internal _datatype? DenseMatrix.prototype.getDataType = function () { return getArrayDataType(this._data, typeOf) } @@ -145,6 +146,7 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies * new matrix elements will be filled with zeros. */ DenseMatrix.prototype.subset = function (index, replacement, defaultValue) { + if (isIndex(index)) index = Matrix.parseWithinIndex(index, this._size) switch (arguments.length) { case 1: return _get(this, index) @@ -159,6 +161,20 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies } } + /** + * Get one of the top-level sections of matrix one level down, e.g. + * the `which`th row of a 2D matrix, or the `which`th scalar of a + * vector. + * + * @memberof DenseMatrix + * @param {number} which + * @return {DenseMatrix | scalar} the `which`th element or full section + */ + DenseMatrix.prototype.layer = function (which) { + if (this._size.length === 1) return this._data[which] + return this.create(this._data[which], this._datatype) + } + /** * Get a single element from the matrix. * @memberof DenseMatrix diff --git a/src/type/matrix/Matrix.js b/src/type/matrix/Matrix.js index 06ab6c162a..c5b3173833 100644 --- a/src/type/matrix/Matrix.js +++ b/src/type/matrix/Matrix.js @@ -1,4 +1,5 @@ import { factory } from '../../utils/factory.js' +import { parseRange } from '../../utils/collection.js' const name = 'Matrix' const dependencies = [] @@ -7,15 +8,19 @@ export const createMatrixClass = /* #__PURE__ */ factory(name, dependencies, () /** * @constructor Matrix * - * A Matrix is a wrapper around an Array. A matrix can hold a multi dimensional - * array. A matrix can be constructed as: + * A Matrix is an object representing a matrix with any number of + * dimensions. A matrix can be generated in math js from an Array (with + * nested-Array depth equal to the dimension), via: * * let matrix = math.matrix(data) * - * Matrix contains the functions to resize, get and set values, get the size, - * clone the matrix and to convert the matrix to a vector, array, or scalar. - * Furthermore, one can iterate over the matrix using map and forEach. - * The internal Array of the Matrix can be accessed using the function valueOf. + * This Matrix interface contains the functions to resize, + * get and set values, get the size, clone the matrix and to convert + * the matrix to a vector, array, or scalar. + * Furthermore, one can iterate over the matrix using map and forEach and + * JavaScript for loops. + * A (nested) Array corresponding to the Matrix can be accessed using the + * function valueOf. * * Example usage: * @@ -91,6 +96,72 @@ export const createMatrixClass = /* #__PURE__ */ factory(name, dependencies, () throw new Error('Cannot invoke subset on a Matrix interface') } + /** + * Get one of the full sections of the matrix of dimension one less, + * at a specific position among all such sections. Note that + * `M.layer(n)` is equivalent to `M.subset(new Index(n, ':', ':' ...))` + * with the proper number of wildcards to match the dimension of matrix M, + * but typically much faster. + * + * For example, if M is a vector, then `M.layer(0)` is its first element, + * whereas if M is an ordinary 2D matrix, then `M.layer(1)` is its second + * row. + */ + Matrix.prototype.layer = function (which) { + // must be provided by each Matrix implementation + throw new Error('Cannot invoke layer on a Matrix interface') + } + + /** + * Helper for all of the implementations' subset methods. + * Parses any string representations of Ranges in the index, filling in + * limits with the sizes of the corresponding dimensions, allowing for + * wildcards. + * + * @param {Index} index + * @param {number[]} size of the matrix + * @return {Index} same index with string Ranges parsed and limits filled + */ + Matrix.parseWithinIndex = function (index, size) { + let altered = false + const ndim = index.size().length + const newRanges = [] + for (let dim = 0; dim < ndim; ++dim) { + let spec = index.dimension(dim) + if (typeof spec === 'string') { + const fields = parseRange(spec) + if (fields === null) { + throw new Error(`String '${spec}' does not specify a Range`) + } + fields.step ||= '1' + for (const key in fields) { + if (fields[key] === '') continue + const val = Number(fields[key]) + if (isNaN(val)) { + throw new SyntaxError( + `${key} in '${spec}' does not represent a number`) + } + fields[key] = val + } + if (fields.start === '') fields.start = index.shiftPosition + if (fields.end === '') fields.end = size[dim] + index.shiftPosition + fields.start -= index.shiftPosition + fields.end -= index.shiftPosition + const attributes = index.includeEnd + ? { start: fields.start, last: fields.end, step: fields.step } + : fields + if (!Matrix.createRange) { + throw new Error('Range has not injected its constructor into Matrix') + } + altered = true + spec = Matrix.createRange(attributes) + } + newRanges.push(spec) + } + if (altered) return index.create(newRanges) + return index + } + /** * Get a single element from the matrix. * @param {number[]} index Zero-based index @@ -141,7 +212,7 @@ export const createMatrixClass = /* #__PURE__ */ factory(name, dependencies, () * * @return {Matrix} The reshaped matrix */ - Matrix.prototype.reshape = function (size, defaultValue) { + Matrix.prototype.reshape = function (size) { // must be implemented by each of the Matrix implementations throw new Error('Cannot invoke reshape on a Matrix interface') } diff --git a/src/type/matrix/MatrixIndex.js b/src/type/matrix/MatrixIndex.js index 00f5447349..41d1aeae72 100644 --- a/src/type/matrix/MatrixIndex.js +++ b/src/type/matrix/MatrixIndex.js @@ -9,21 +9,28 @@ const dependencies = ['ImmutableDenseMatrix', 'getMatrixDataType'] export const createIndexClass = /* #__PURE__ */ factory(name, dependencies, ({ ImmutableDenseMatrix, getMatrixDataType }) => { /** * Create an index. An Index can store ranges and sets for multiple dimensions. - * Matrix.get, Matrix.set, and math.subset accept an Index as input. + * The math.subset() function accepts an Index as input. * * Usage: * const index = new Index(range1, range2, matrix1, array1, ...) * * Where each parameter can be any of: * A number - * A string (containing a name of an object property) - * An instance of Range * An Array with the Set values * An Array with Booleans - * A Matrix with the Set values + * A Matrix with the Set values (this might often be a Range instance) * A Matrix with Booleans + * A string (will be interpreted as the name of an object property when + * used to index an object, or converted into a Range when used + * to index a Matrix/Array) * - * The parameters start, end, and step must be integer numbers. + * Note that all numeric values provided will be converted to the ordinary + * JavaScript number type when used for indexing. + * Further, once an Index has been constructed, you can set the `includeEnd` + * property on the Index to indicate that when strings are converted to + * Ranges, the end should be included (rather than excluded as by default). + * Similarly, you can set a `shiftPosition` property that will be subtracted + * from the entries of Ranges constructed from strings. * * @class Index * @Constructor Index @@ -37,6 +44,8 @@ export const createIndexClass = /* #__PURE__ */ factory(name, dependencies, ({ I this._dimensions = [] this._sourceSize = [] this._isScalar = true + this.includeEnd = false + this.shiftPosition = 0 for (let i = 0, ii = ranges.length; i < ii; i++) { const arg = ranges[i] @@ -45,7 +54,7 @@ export const createIndexClass = /* #__PURE__ */ factory(name, dependencies, ({ I const argType = typeof arg let sourceSize = null if (isRange(arg)) { - this._dimensions.push(arg) + this._dimensions.push(arg.toNumber()) this._isScalar = false } else if (argIsArray || argIsMatrix) { // create matrix @@ -67,12 +76,13 @@ export const createIndexClass = /* #__PURE__ */ factory(name, dependencies, ({ I this._dimensions.push(Number(arg)) } else if (argType === 'string') { // object property (arguments.count should be 1) + // or string notation for a Range, possibly with elided limits + // (see documention for `index` function) to allow wildcard this._dimensions.push(arg) } else { throw new TypeError('Dimension must be an Array, Matrix, number, bigint, string, or Range') } this._sourceSize.push(sourceSize) - // TODO: implement support for wildcard '*' } } @@ -116,7 +126,7 @@ export const createIndexClass = /* #__PURE__ */ factory(name, dependencies, ({ I * @return {Index} index * @private */ - Index.create = function (ranges) { + Index.prototype.create = function (ranges) { const index = new Index() Index.apply(index, ranges) return index @@ -290,7 +300,7 @@ export const createIndexClass = /* #__PURE__ */ factory(name, dependencies, ({ I * @return {Index} */ Index.fromJSON = function (json) { - return Index.create(json.dimensions) + return new Index(...json.dimensions) } return Index diff --git a/src/type/matrix/Range.js b/src/type/matrix/Range.js index 0025ce3728..d5d5edcf35 100644 --- a/src/type/matrix/Range.js +++ b/src/type/matrix/Range.js @@ -1,128 +1,529 @@ -import { isBigInt, isBigNumber } from '../../utils/is.js' -import { format, sign, nearlyEqual } from '../../utils/number.js' +import { getArrayDataType } from '../../utils/array.js' import { factory } from '../../utils/factory.js' +import { parseRange } from '../../utils/collection.js' +import { + isBigInt, isBigNumber, isCollection, isComplex, isFraction, + isIndex, isMatrix, isNumber, isRange, isUnit +} from '../../utils/is.js' const name = 'Range' -const dependencies = [] +const dependencies = [ + 'typed', 'typeOf', '?Index', '?BigNumber', '?Fraction', '?Complex', + 'Matrix', '?DenseMatrix', 'size', 'getMatrixDataType', + 'one', 'zero', 'add', 'subtract', 'multiply', 'divide', 'scalarDivide', + 'floor', 'equal', 'deepEqual', 'smallerEq', 'largerEq', 'isZero', 'isBounded', + 'number', 'numeric', 'format' +] -export const createRangeClass = /* #__PURE__ */ factory(name, dependencies, () => { +// Some optimized operator functions used for special cases below. We +// make them constants up here rather than generate them on the fly so that +// they will not disrupt `deepStrictEqual` +const identity = n => n +const toBigInt = n => BigInt(n) +const byBigInt = (n, b) => BigInt(n) * b + +export const createRangeClass = /* #__PURE__ */ factory(name, dependencies, ({ + typed, typeOf, Index, BigNumber, Fraction, Complex, + Matrix, DenseMatrix, size, getMatrixDataType, + one, zero, add, subtract, multiply, divide, scalarDivide, + floor, equal, deepEqual, smallerEq, largerEq, isZero, isBounded, + number, numeric, format +}) => { + // Helpers for constructor; note the canonical attributes correspond positionally + // two-to-one with the first list of attributes that are available for external use + const attrs = 'start,end,step,length,last'.split(',') + const enoughAttrs = ['start', 'step', 'length'] // determine range uniquely + function isAttrs (thing) { + if (!thing) return false + if (typeof thing !== 'object') return false + for (const key in thing) if (!attrs.includes(key)) return false + return true + } + function getBdSegments (attributes) { + let bound = null + let segments = attributes.length + if ('last' in attributes) { + bound = attributes.last + segments -= 1 + } else bound = attributes.end + return [bound, segments] + } + const deepZero = entity => deepEqual(entity, zero(entity)) + function oneUnit (u) { + const result = u.clone() + let val = one(u.value) + // Adjust by the prefixes of the unit + for (const spec of u.units) val = multiply(val, spec.prefix.value) + result.value = val + return result + } + function firstEntry (collection) { + if (Array.isArray(collection)) { + while (size(collection).length) collection = collection(0) + return collection + } + const pos = size(collection).map(() => 0) + return collection.get(pos) + } + + // More optimized operator functions, see above. + const toBigNumber = n => new BigNumber(n) + const toFraction = n => new Fraction(n) + const toComplex = n => new Complex(n) + const multBigIntArray = typed.find(multiply, ['bigint', 'Array']) + const arrayByBigint = (n, b) => multBigIntArray(BigInt(n), b) /** - * Create a range of numbers. A range has a start, step, and end, - * and contains functions to iterate over the range. + * Range Matrix implementation. + * + * A Range is a matrix representing an arithmetic sequence. The + * elements of a Range consist of the values of `a + sd`, where + * `a` and `d` are any entities for which the relevant operations are + * defined, and `s` is an integer number from 0 to one less than the + * length of the Range. A common case is for `a` to be an integer number + * and `d` to be 1, in which case the Range becomes a vector of + * consecutive numbers. Ranges whose elements can be converted to numbers + * may be used to index other Matrices. + * + * Note that Ranges are "lazy" in that the entries are not stored in memory, + * but generated as needed. As a consequence, attempts to alter individual + * entries of a Range will throw an error. + * + * Every Range has several attributes that determine its entries. Once + * constructed, these attributes cannot be changed; they are read-only. + * + * Every Range has these attributes: + * * start: the first element of the Range (the value `a` above). + * * step: the step or common difference of the Range (the value `d` above). + * * length: the number of elements in the Range, or one more than the + * largest value of `s` above. Note that this attribute may be Infinity, + * so that a Range can represent an unending arithmetic progression. + * + * In addition, a Range may have one or both of the following attributes: + * * last: the inclusive final limit of the Range. This value must be + * of the form `a + td` for some number `t`, in which case the Range + * consists of `a + sd` for all nonnegative integers `s ≤ t`. + * * end: an exclusive limit of the Range. This value must be of the + * form `a + ud` for some number `u`, in which case the Range consists + * of `a + sd` for all nonnegative integers `s < u`. * - * A range can be constructed as: + * There is a consistency relation, in that if the last value is the start + * value plus _t_ times the step value, then the length must be the floor + * of _t_ plus one. Similarly, if the end value is the start value plus _u_ + * times the step, then the length must be the ceiling of u. If the step + * of a Range is zero, then it generally does not have an end or last value + * to avoid breaking this consistency relation. * - * const range = new Range(start, end) - * const range = new Range(start, end, step) + * A Range can be constructed from a plain object with any of the above + * attributes, presuming they are consistent. In addition, for convenience + * and backward compatibility, the first constructor argument (if any) that + * is not of this form gives the start value, the second gives the end + * value, and the third gives the step value. * - * Note that the endpoints and step may be specified with other numeric - * types such as bigint or BigNumber, but they will be demoted to the - * built-in `number` type and the Range will only contain numbers. The - * rationale for this demotion is that Range objects are primarily used - * for indexing Matrix objects, and Matrix objects may only be indexed - * with `number`s. + * Because of the consistency relation and defaults provided for convenience, + * some or even all of the attributes may be missing in the constructor. + * If any are missing, they are deduced for you in the following order: + * * step: filled in via consistency if start, length, and at least one + * of last and end are specified; otherwise set to the "one" value of + * the type of start, last, or end if specified, or the number 1 if not. + * * start: filled in via consistency with the step if length and at + * least one of last and end are specified; otherwise set to the "zero" + * value of the type of last or end if specified, or the number 0 if not. + * * length: filled in via consistency with start and step if at least + * one of last and end are specified; otherwise, set to 0. * - * To get the result of the range: - * range.forEach(function (x) { - * console.log(x) - * }) - * range.map(function (x) { - * return math.sin(x) - * }) - * range.toArray() + * In addition, if the length value is finite and the step is nonzero, the + * following are set whether or not they were specified, to canonicalize + * the attributes of the Range (which makes it easier to use and interpret): + * * last: Set to the start value plus the step times the length + * minus one. + * * end: Set to the start value plus the step times the length. * - * Example usage: + * Note that the endpoints and increment may be specified with any type + * handled by mathjs, but they must support the operations needed by Range + * (addition, multiplication by an integer ordinary number, comparison). + * The data type of the range is the data type of `start + n*step`, where `n` + * is an integer number; the package assumes that this data type does not + * depend on the value of `n`. * - * const c = new Range(2, 6) // 2:1:5 - * c.toArray() // [2, 3, 4, 5] - * const d = new Range(2, -3, -1) // 2:-1:-2 - * d.toArray() // [2, 1, 0, -1, -2] + * Ranges support any non-modifying Matrix methods. + * + * Examples: + * + * const c = new Range(2, 5) + * c.toArray() // [2, 3, 4] + * const b = new Range({start: 2, last: 5}) + * b.toArray() // [2, 3, 4, 5] + * new Range({start: 2, end: 5}) // [2, 3, 4] + * + * const d = new Range(2, -2, -1) + * d.toArray() // [2, 1, 0, -1] + * const d2 = new Range(2, {step: -1}, -2) // same Range + * + * const e = new Range(3n) // 3n, 4n, 5n, ... forever + * e.toArray() // throws + * const f = new Range() // [] + * + * const g = new Range({start: 9, step: fraction(2, 3), end: 11}) + * g.toArray() // [fraction(9), fraction(29, 3), fraction(31, 3)] * * @class Range * @constructor Range - * @param {number} start included lower bound - * @param {number} end excluded upper bound - * @param {number} [step] step size, default value is 1 + * @param {number} [start] included lower bound + * @param {number} [end] excluded upper bound + * @param {number} [step] step size, default value is 1 */ - function Range (start, end, step) { + function Range (...specs) { if (!(this instanceof Range)) { throw new SyntaxError('Constructor must be called with the new operator') } - const hasStart = start !== null && start !== undefined - const hasEnd = end !== null && end !== undefined - const hasStep = step !== null && step !== undefined - - if (hasStart) { - if (isBigNumber(start)) { - start = start.toNumber() - } else if (typeof start !== 'number' && !isBigInt(start)) { - throw new TypeError('Parameter start must be a number or bigint') + const attributes = {} + // Read the first object supplying attributes, if any + for (const spec of specs) { + if (isAttrs(spec)) { + Object.assign(attributes, spec) + break } } - if (hasEnd) { - if (isBigNumber(end)) { - end = end.toNumber() - } else if (typeof end !== 'number' && !isBigInt(end)) { - throw new TypeError('Parameter end must be a number or bigint') + let role = 0 + let attrCount = 0 + // Now interpret the positional arguments, ignoring one attributes object + for (const spec of specs) { + if (isAttrs(spec)) { + attrCount += 1 + if (attrCount > 1) { + throw new Error('Only one attributes object may specify a Range') + } + } else { + if (role === 3) { + throw new Error( + 'Only start, end, and step allowed as positional arguments ' + + 'of Range constructor') + } + const key = attrs[role] + if (key in attributes) { + throw new Error( + `May not specify Range attribute "${key}" via key and argument.`) + } + attributes[key] = spec + role += 1 + } + } + + // OK, we have extracted all of the specified attributes. Now fill in + // the rest/canonicalize them as specified. + // First make sure the length is a number + if ('length' in attributes) { + attributes.length = number(attributes.length) + } + // Now deduce "step" if necessary + if (attributes.step === undefined || attributes.step === null) { + const prereqs = 'start' in attributes && 'length' in attributes + if (prereqs && ('last' in attributes || 'end' in attributes)) { + const [bound, segments] = getBdSegments(attributes) + if (segments === 0) attributes.step = zero(attributes.start) + else { + const span = subtract(bound, attributes.start) + // if the span is computed in bigints, we want a bigint increment + let bigi = isBigInt(span) + bigi ||= isMatrix(span) && span.datatype() === 'bigint' + bigi ||= Array.isArray(span) && + getArrayDataType(span, typeOf) === 'bigint' + const denominator = bigi ? BigInt(segments) : segments + attributes.step = divide(span, denominator) + } + } else { + let template = attributes.start + if (template === undefined) { + if ('last' in attributes) template = attributes.last + else if ('end' in attributes) template = attributes.end + else template = 1 + } + if (size(template).length) template = firstEntry(template) + // now we have a scalar! + if (isUnit(template)) attributes.step = oneUnit(template) + else attributes.step = one(template) } + } else if (!isBounded(attributes.step)) { + throw new RangeError('A Range must have a finite increment') + } + // Now that we have the increment b, we can choose the multiplication + // operation. For n an integer JavaScript number, we want n*b to be the sum + // of n copies of b. If we simply use mathjs multiply, this property will + // hold for most types b might have, but not for bigint (because e.g. + // 1.657 * 3n should be 4.971, so mathjs makes that combination always + // return number, not bigint). So we need to take care in choosing what + // function we will use to multiply: + this.times = typed.find(multiply, ['number', typeOf(attributes.step)]) + const incr = attributes.step + // Special cases for times (for speedup when increment is 1) + if (!isUnit(incr) && equal(incr, 1)) { + if (isNumber(incr)) this.times = identity + else if (isBigInt(incr)) this.times = toBigInt + else if (isBigNumber(incr)) this.times = toBigNumber + else if (isFraction(incr)) this.times = toFraction + else if (isComplex(incr)) this.times = toComplex + } else if (isBigInt(incr)) { // and special cases b/c of bigint conversions + this.times = byBigInt + } else if (isMatrix(incr) && incr.datatype() === 'bigint') { + const mult = typed.find(multiply, ['bigint', typeOf(incr)]) + this.times = (n, b) => mult(BigInt(n), b) + } else if (Array.isArray(incr) && getArrayDataType(incr) === 'bigint') { + this.times = arrayByBigint + } + + // Next deduce "start" if necessary + if (attributes.start === undefined || attributes.start === null) { + if ('length' in attributes && + ('last' in attributes || 'end' in attributes) + ) { + const [bound, segments] = getBdSegments(attributes) + attributes.start = subtract( + bound, this.times(segments, attributes.step)) + } else if ('last' in attributes) { + attributes.start = zero(attributes.last) + } else if ('end' in attributes) { + attributes.start = zero(attributes.end) + } else attributes.start = zero(attributes.step) + } + if (!isBounded(attributes.start)) { + throw new RangeError('A Range must start on a finite value') + } + + // Now deduce length if need be + if (attributes.length === undefined || attributes.length === null) { + if ('last' in attributes) { + if (!isBounded(attributes.last)) attributes.length = Infinity + else { + const diff = subtract(attributes.last, attributes.start) + let rawLength = scalarDivide(diff, attributes.step) + if (rawLength === undefined) { + if (size(diff).length > 0 && size(attributes.step) === 0) { + const first = firstEntry(diff) + if (deepZero(subtract(diff, first))) { + rawLength = scalarDivide(first, attributes.step) + } + } + } + if (rawLength === undefined) { + let message = `No scalar multiple of ${attributes.step} takes ` + message += `${attributes.start} to ${attributes.last}` + throw new Error(message) + } + attributes.length = Math.floor(number(rawLength)) + 1 + } + } else if ('end' in attributes) { + if (!isBounded(attributes.end)) attributes.length = Infinity + else { + const diff = subtract(attributes.end, attributes.start) + let rawLength = scalarDivide(diff, attributes.step) + if (rawLength === undefined) { + if (size(diff).length > 0 && size(attributes.step).length === 0) { + const first = firstEntry(diff) + if (deepZero(subtract(diff, first))) { + rawLength = scalarDivide(first, attributes.step) + } + } + } + if (rawLength === undefined) { + let message = `No scalar multiple of ${attributes.step} takes ` + message += `${attributes.start} to ${attributes.end}` + throw new Error(message) + } + attributes.length = Math.ceil(number(rawLength)) + } + } else attributes.length = 0 } - if (hasStep) { - if (isBigNumber(step)) { - step = step.toNumber() - } else if (typeof step !== 'number' && !isBigInt(step)) { - throw new TypeError('Parameter step must be a number or bigint') + if (attributes.length < 0) attributes.length = 0 + + // Finally fill in last and end as appropriate + if (Number.isFinite(attributes.length)) { + attributes.length = Math.floor(attributes.length) + // canonicalize limits + if (deepZero(attributes.step)) { + // We certainly know the last entry: + attributes.last = attributes.start + // But there is no way to have an exclusive limit unless the + // length is zero + attributes.end = + attributes.length === 0 ? attributes.start : undefined + } else { + attributes.last = add( + attributes.start, this.times(attributes.length - 1, attributes.step)) + attributes.end = add(attributes.last, attributes.step) } + } else { + attributes.last = undefined + attributes.end = undefined } - this.start = hasStart ? parseFloat(start) : 0 - this.end = hasEnd ? parseFloat(end) : 0 - this.step = hasStep ? parseFloat(step) : 1 - if (hasStep && nearlyEqual(this.step, 0)) { - throw new Error('Step must not be zero') + // Canonicalize the type of start to match all the other values: + attributes.start = add(attributes.start, this.times(0, attributes.step)) + + // set up data type and remaining operations + this.plus = typed.find( + add, [typeOf(attributes.start), typeOf(this.times(1, attributes.step))]) + const startsize = size(attributes.start) + this.subcollection = !!(startsize.length) + if (this.subcollection) { + this._datatype = getMatrixDataType(attributes.start) + } else this._datatype = typeOf(attributes.start) + + this._size = [attributes.length, ...startsize] + + // Finally, set up the read-only properties: + for (const key of attrs) { + Object.defineProperty(this, key, { + value: attributes[key], + enumerable: true + }) } } + Range.prototype = new Matrix() + /** * Attach type information */ + Object.defineProperty(Range, 'name', { value: 'Range' }) + Range.prototype.constructor = Range Range.prototype.type = 'Range' Range.prototype.isRange = true /** - * Parse a string into a range, - * The string contains the start, optional step, and end, separated by a colon. + * [DEPRECATED; use `math.parse` directly] Parse a string into a range. + * The string contains the start, optional step, and end, separated by colons. * If the string does not contain a valid range, null is returned. + * Note that currently only ordinary Javascript floating-point number + * items are permitted for start, step, and end in this string notation. * For example str='0:2:11'. + * The default step, if it is not specified, is 1. + * If the string begins with a ':', 0 is filled in for the first value. + * If it ends with a ':', Infinity is filled in for the last value. + * By default, the end value is excluded from the range. + * * @memberof Range * @param {string} str + * @param {?number} limit * @return {Range | null} range */ Range.parse = function (str) { - if (typeof str !== 'string') { - return null + if (Range.parseMethodMustWarn) { + console.warn( + 'Using deprecated class method Range.parse(); ' + + 'use library function math.parse() instead.') + Range.parseMethodMustWarn = false } - const args = str.split(':') - const nums = args.map(function (arg) { - return parseFloat(arg) - }) - - const invalid = nums.some(function (num) { - return isNaN(num) - }) - if (invalid) { - return null + if (typeof str !== 'string') return null + const fields = parseRange(str) + if (fields === null) return null + if (fields.start === '') fields.start = '0' + if (fields.end === '') fields.end = 'Infinity' + if (fields.step === '') fields.step = '1' + for (const key in fields) { + const value = Number(fields[key]) + if (isNaN(value)) return null + fields[key] = value } + return new Range(fields) + } + // inject Range constructor into parent class, + // for use by all Matrix implementations + Matrix.createRange = function (...args) { + return new Range(...args) + } + + /** + * Get the datatype of the entries of the range. + * + * Usage: + * const format = range.datatype() // retrieve renge datatype + * + * @memberof Range + * @return {string} The datatype. + */ + Range.prototype.datatype = function () { + return this._datatype + } + + /** + * Get the matrix type + * + * Usage: + * const matrixType = matrix.getDataType() // retrieves the matrix type + * + * @memberOf Range + * @return {string} type information + */ + Range.prototype.getDataType = function () { + return this._datatype + } - switch (nums.length) { - case 2: - return new Range(nums[0], nums[1]) - case 3: - return new Range(nums[0], nums[2], nums[1]) - default: - return null + /** + * Get the storage format used by the matrix. + * + * Usage: + * const format = range.storage() // retrieve storage format + * + * @return {string} The storage format. + */ + Range.prototype.storage = function () { + return 'range' // neither 'sparse' or 'dense' seemed fair + } + + /** + * Create a new Range from data if possible, otherwise DenseMatrix + * + * Note that to conform with the Matrix prototype, this should accept + * an Array of the data and the datatype of the data. Hence, it attempts + * to "reverse engineer" a Range that will encode that data with the + * proper datatype, and if not, reverts to creating a DenseMatrix. + * + * @memberof Range + * @param {Array} data + * @param {string} [datatype] + */ + Range.prototype.create = function (data, datatype) { + if (data.length === 0) { + return new Range({ + start: numeric(0, datatype), + end: numeric(0, datatype) + }) + } + let start = data[0] + if (datatype) start = numeric(start, datatype) + if (data.length === 1) return new Range({ start, last: start }) + let last = data[data.length - 1] + if (datatype) last = numeric(last, datatype) + if (data.length === 2) { + return new Range({ start, length: 2, step: subtract(last, start) }) + } + let entry = data[1] + if (datatype) entry = numeric(entry, datatype) + const step = subtract(entry, start) + for (let i = 2; i < data.length; ++i) { + entry = add(entry, step) + if (!equal(entry, data[i])) { + if (DenseMatrix) return new DenseMatrix(data, datatype) + throw new Error('Data supplied is not in the form of a Range') + } } + return new Range({ start, length: data.length, step }) + } + + /** + * Create a new Range + * + * Convenience method to call the constructor from an instance of Range. + * Takes exactly the same possible arguments as the constructor. + * + * @memberof Range + * @param {..args} arguments + * @return {Range} fresh Range + */ + Range.prototype.createRange = function (...args) { + return new Range(...args) } /** @@ -130,32 +531,149 @@ export const createRangeClass = /* #__PURE__ */ factory(name, dependencies, () = * @return {Range} clone */ Range.prototype.clone = function () { - return new Range(this.start, this.end, this.step) + const spec = {} + for (const key of enoughAttrs) spec[key] = this[key] + return new Range(spec) } /** - * Retrieve the size of the range. - * Returns an array containing one number, the number of elements in the range. + * Get a subset of the range (replacement prohibited) + * + * Usage: + * const subset = range.subset(index) // retrieve subset + * + * @param {Index} index + */ + + Range.prototype.subset = function (index, replacement, defaultValue) { + if (replacement || defaultValue) { + throw new Error('Ranges are immutable, cannot replace entries') + } + if (!isIndex(index)) throw new TypeError('Invalid index') + index = Matrix.parseWithinIndex(index, this._size) + const wanted = index.dimension(0) + const sizes = index.size() + if (isNumber(wanted)) { + const item = this.layer(wanted) + if (sizes.length === 1) return wanted + if (!Index) { + throw new Error('No Indexing into 2D Range without Matrix support') + } + // Caller thinks we can index into the result, have at it + const newIndex = [] + for (let d = 1; d < sizes.length; ++d) newIndex.push(index.dimension(d)) + return item.subset(new Index(...newIndex)) + } + if (!isRange(wanted) || sizes.length > 1) { + // Punt to Matrix subsetting + if (!DenseMatrix) { + throw new Error('No general subset of Range without Matrix support') + } + return new DenseMatrix(this.toArray(), this._datatype).subset(index) + } + + // Indexing a range by a single range produces a range + if (this.length < wanted.length) { + throw new Error('Cannot subset a Range by a longer Range') + } + if (!Number.isFinite(wanted.length)) { + return new Range({ + start: this.layer(wanted.start), + step: this.step * wanted.step + }) + } + return new Range({ + start: this.layer(wanted.start), + end: this.layer(wanted.end), + step: this.step * wanted.step + }) + } + + /** + * Get the entry at an integer position in a Range. * @memberof Range - * @returns {number[]} size + * @param {number} which Zero-based position + * @return {*} value */ - Range.prototype.size = function () { - let len = 0 - const start = this.start - const step = this.step - const end = this.end - const diff = end - start + Range.prototype.layer = function (index) { + if (index < 0 || index >= this.length) { + throw new RangeError('index out of Range') + } + return this.plus(this.start, this.times(index, this.step)) + } - if (sign(step) === sign(diff)) { - len = Math.ceil((diff) / step) - } else if (diff === 0) { - len = 0 + /** + * Get a single element from a Range. + * @memberof Range + * @param {number[]} index Zero-based index + * @return {*} value + */ + Range.prototype.get = function (index) { + const item = this.layer(index[0]) + if (index.length === 1) return item + return item.get(index.slice(1)) + } + + /** + * Replacing a single element of a Range is not supported + */ + Range.prototype.set = function () { + throw new Error( + 'Replacement of an element of a Range is not supported') + } + + /** + * Resize the Range to the given size. Returns a fresh Matrix when + * `copy=true`, otherwise fails because a Range cannot be resized in place. + * + * @memberof Range + * @param {number[] || Matrix} size The new size the matrix should have. + * @param {*} [defaultValue=0] Default value, filled in on new entries. + * If not provided, the matrix elements will + * be filled with zeros. + * @param {boolean} [copy] Return a resized copy of the matrix + * + * @return {Matrix} The resized matrix + */ + Range.prototype.resize = function (size, defaultValue, copy) { + // validate arguments + if (!isCollection(size)) { + throw new TypeError('Array or Matrix expected for new size') + } + if (!copy) throw new Error('A Range cannot be resized in place') + if (!DenseMatrix) { + throw new Error('No Range resize without Matrix support') } + const mat = new DenseMatrix(this.valueOf(), this._datatype) + return mat.resize(size, defaultValue) + } - if (isNaN(len)) { - len = 0 + /** + * Reshape the Range to the given size. Returns a copy of the matrix when + * `copy=true`, otherwise fails because a Range cannot be reshaped. + * + * @memberof Range + * @param {number[]} size The new size the matrix should have. + * @param {boolean} [copy] Return a reshaped copy of the matrix + * + * @return {Matrix} The reshaped matrix + */ + Range.prototype.reshape = function (size, copy) { + if (!copy) throw new Error('A Range cannot be reshaped in place') + if (!DenseMatrix) { + throw new Error('No Range reshape without Matrix support') } - return [len] + return new DenseMatrix(this.valueOf(), this._datatype).reshape(size) + } + + /** + * Retrieve the size of the Range. + * Returns an array containing one number, the number of elements in the range. + * @memberof Range + * @returns {number[]} size + */ + Range.prototype.size = function () { + return this._size } /** @@ -164,19 +682,16 @@ export const createRangeClass = /* #__PURE__ */ factory(name, dependencies, () = * @return {number | undefined} min */ Range.prototype.min = function () { - const size = this.size()[0] - - if (size > 0) { - if (this.step > 0) { - // positive step - return this.start - } else { - // negative step - return this.start + (size - 1) * this.step - } - } else { - return undefined + if (this.length === 0) return undefined + if (this.length === 1) return this.start + if (this.subcollection) { + throw new TypeError('Elements of sequence are collections, so unordered') + } + if (Number.isFinite(this.length)) { + return smallerEq(this.start, this.last) ? this.start : this.last } + // Infinite sequence + return smallerEq(this.start, this.layer(1)) ? this.start : undefined } /** @@ -185,76 +700,161 @@ export const createRangeClass = /* #__PURE__ */ factory(name, dependencies, () = * @return {number | undefined} max */ Range.prototype.max = function () { - const size = this.size()[0] - - if (size > 0) { - if (this.step > 0) { - // positive step - return this.start + (size - 1) * this.step - } else { - // negative step - return this.start - } - } else { - return undefined + if (this.length === 0) return undefined + if (this.length === 1) return this.start + if (this.subcollection) { + throw new TypeError('Elements of sequence are collections, so unordered') } + if (Number.isFinite(this.length)) { + return largerEq(this.start, this.last) ? this.start : this.last + } + // Infinite sequence + return largerEq(this.start, this.layer(1)) ? this.start : undefined } /** - * Execute a callback function for each value in the range. + * Execute a callback function for each value in theRrange. * @memberof Range * @param {function} callback The callback method is invoked with three * parameters: the value of the element, the index * of the element, and the Range being traversed. + * @param {boolean} skipZeros If true, the callback function is invoked only for non-zero entries + * @param {boolean} isUnary If true, the callback function is invoked with one parameter */ - Range.prototype.forEach = function (callback) { + Range.prototype.forEach = function ( + callback, skipZeros = false, isUnary = false + ) { + if (!Number.isFinite(this.length)) throw new Error('Attempt to infinite loop') let x = this.start - const step = this.step - const end = this.end - let i = 0 - - if (step > 0) { - while (x < end) { - callback(x, [i], this) - x += step - i++ - } - } else if (step < 0) { - while (x > end) { - callback(x, [i], this) - x += step - i++ + for (let i = 0; i < this.length; ++i) { + if (this.subcollection) { + if (isUnary) x.forEach(callback, skipZeros, isUnary) + else { + const me = this + x.forEach((val, ix) => callback(val, [i, ...ix], me), skipZeros) + } + } else { + if (skipZeros && isZero(x)) continue + if (isUnary) callback(x) + else callback(x, [i], this) } + x = this.plus(x, this.step) + } + } + + /** + * Iterate over the range elements + * @return {Iterable<{ value, index: number[] }>} + */ + Range.prototype[Symbol.iterator] = function * () { + let x = this.start + for (let i = 0; i < this.length; ++i) { + if (this.subcollection) { + for (const { value, ix } of x) yield ({ value, index: [i, ...ix] }) + } else yield ({ value: x, index: [i] }) + x = this.plus(x, this.step) } } /** * Execute a callback function for each value in the Range, and return the - * results as an array + * results as a Matrix * @memberof Range * @param {function} callback The callback method is invoked with three * parameters: the value of the element, the index - * of the element, and the Matrix being traversed. + * of the element, and the Range being traversed. * @returns {Array} array */ - Range.prototype.map = function (callback) { + Range.prototype.map = function ( + callback, skipZeros = false, isUnary = false + ) { + if (!Number.isFinite(this.length)) { + throw new Error( + 'Attempt to infinite loop Range with ' + + `start=${this.start} step=${this.step}`) + } const array = [] - this.forEach(function (value, index, obj) { - array[index[0]] = callback(value, index, obj) - }) + let x = this.start + for (let i = 0; i < this.length; ++i) { + if (this.subcollection) { + if (isUnary) array.push(x.map(callback, skipZeros, isUnary)) + else { + const me = this + array.push( + x.map((val, ix) => callback(val, [i, ...ix], me), skipZeros)) + } + } else { + if (skipZeros && isZero(x)) continue + if (isUnary) array.push(callback(x)) + else array.push(callback(x, [i], this)) + } + x = this.plus(x, this.step) + } + if (DenseMatrix) return new DenseMatrix(array) return array } /** - * Create an Array with a copy of the Ranges data + * Returns an array containing the rows of a 2D Range + * @returns {Array} + */ + Range.prototype.rows = function () { + if (!Number.isFinite(this.length)) { + throw new Error('Attempt to infinite loop') + } + if (this._size.length !== 2) { + throw new TypeError('Rows can only be returned for a 2D matrix.') + } + const result = [] + let x = this.start + for (let i = 0; i < this.length; ++i) { + if (DenseMatrix) result.push(new DenseMatrix(x, this._datatype)) + else result.push(x) + x = this.plus(x, this.step) + } + return result + } + + /** + * Returns an array containing the columns of a 2D Range + * @returns {Array} + */ + Range.prototype.columns = function () { + if (!Number.isFinite(this.length)) { + throw new Error('Attempt to infinite loop') + } + if (this._size.length !== 2) { + throw new TypeError('Rows can only be returned for a 2D matrix.') + } + const colArrays = [] + let x = this.start + if (this.length) for (const { value } of x) colArrays.push([value]) + for (let i = 1; i < this.length; ++i) { + x = this.plus(x, this.step) + for (const { value, ix } of x) colArrays[ix[0]].push(value) + } + if (DenseMatrix) { + return colArrays.map(arr => new DenseMatrix(arr, this._datatype)) + } + return colArrays + } + + /** + * Create an Array with a copy of the Range data * @memberof Range * @returns {Array} array */ Range.prototype.toArray = function () { + if (!Number.isFinite(this.length)) { + throw new Error('Attempt to infinite loop') + } const array = [] - this.forEach(function (value, index) { - array[index[0]] = value - }) + let x = this.start + for (let i = 0; i < this.length; ++i) { + if (this.subcollection) array.push(x.valueOf()) + else array.push(x) + x = this.plus(x, this.step) + } return array } @@ -271,6 +871,8 @@ export const createRangeClass = /* #__PURE__ */ factory(name, dependencies, () = /** * Get a string representation of the range, with optional formatting options. * Output is formatted as 'start:step:end', for example '2:6' or '0:0.2:11' + * Note that the result is not guaranteed to be parseable unless the + * data type of the Range is `number` * @memberof Range * @param {Object | number | function} [options] Formatting options. See * lib/utils/number:format for a @@ -278,14 +880,29 @@ export const createRangeClass = /* #__PURE__ */ factory(name, dependencies, () = * options. * @returns {string} str */ - Range.prototype.format = function (options) { - let str = format(this.start, options) - - if (this.step !== 1) { - str += ':' + format(this.step, options) + Range.prototype.format = function (options = {}) { + let start = format(this.start, options) + let writeStep = false + if (size(this.step).length > 0) writeStep = true + else { + const canonicalStep = + isUnit(this.step) ? oneUnit(this.step) : one(this.step) + if (!equal(this.step, canonicalStep)) writeStep = true + } + let step = writeStep ? format(this.step, options) : '' + if (typeof this.end === 'undefined') { + // Can't display as usual `start:step:end` so use a new + // pseudo-constructor notation + let str = `Range{start: ${start}, ` + if (step) str += `step: ${step}, ` + return str + `length: ${this.length}}` } - str += ':' + format(this.end, options) - return str + // Display as `start:step:end` but may need parens for nesting + if (isRange(this.start)) start = `(${start})` + if (step && isRange(this.step)) step = `(${step})` + let end = format(this.end, options) + if (isRange(this.end)) end = `(${end})` + return step ? `${start}:${step}:${end}` : `${start}:${end}` } /** @@ -297,31 +914,57 @@ export const createRangeClass = /* #__PURE__ */ factory(name, dependencies, () = return this.format() } + /** + * Return itself if it has datatype number, otherwise a new similar range + * consisting of numbers. + * @memberof Range + * @returns {Range} + */ + Range.prototype.toNumber = function () { + if (this._datatype === 'number') return this + return new Range({ + start: number(this.start), step: number(this.step), length: this.length + }) + } + /** * Get a JSON representation of the range * @memberof Range * @returns {Object} Returns a JSON object structured as: - * `{"mathjs": "Range", "start": 2, "end": 4, "step": 1}` + * `{"mathjs": "Range", ...rangeAttributes}` */ Range.prototype.toJSON = function () { - return { - mathjs: 'Range', - start: this.start, - end: this.end, - step: this.step + const json = { mathjs: 'Range' } + for (const key of enoughAttrs) { + const attr = this[key] + if (attr === undefined || attr === null) continue + if (typeof attr === 'object' && 'toJSON' in attr) { + json[key] = attr.toJSON() + } else json[key] = attr } + return json } /** * Instantiate a Range from a JSON object * @memberof Range * @param {Object} json A JSON object structured as: - * `{"mathjs": "Range", "start": 2, "end": 4, "step": 1}` + * `{"mathjs": "Range", "start": 2, "step": 1, "length": 3}` * @return {Range} */ Range.fromJSON = function (json) { - return new Range(json.start, json.end, json.step) + const spec = {} + for (const key of attrs) { + const item = json[key] + if (item === undefined || item === null) continue + if (typeof item === 'object' && 'fromJSON' in item) { + spec[key] = item.fromJSON() + } else spec[key] = item + } + return new Range(spec) } + Range.parseMethodMustWarn = true + return Range }, { isClass: true }) diff --git a/src/type/matrix/SparseMatrix.js b/src/type/matrix/SparseMatrix.js index 2eba606ce9..7761044d47 100644 --- a/src/type/matrix/SparseMatrix.js +++ b/src/type/matrix/SparseMatrix.js @@ -241,6 +241,14 @@ export const createSparseMatrixClass = /* #__PURE__ */ factory(name, dependencie return rows !== 0 && columns !== 0 ? (this._index.length / (rows * columns)) : 0 } + /** + * Obtaining a layer (i.e. row) of a SparseMatrix is currently not + * supported, as only 2D SparseMatrix is implemented. + */ + SparseMatrix.prototype.layer = function () { + throw new Error('SparseMatrix of dimensions != 2 not yet implemented') + } + /** * Get a subset of the matrix, or replace a subset of the matrix. * @@ -255,8 +263,12 @@ export const createSparseMatrixClass = /* #__PURE__ */ factory(name, dependencie * the matrix is resized. If not provided, * new matrix elements will be filled with zeros. */ - SparseMatrix.prototype.subset = function (index, replacement, defaultValue) { // check it is a pattern matrix - if (!this._values) { throw new Error('Cannot invoke subset on a Pattern only matrix') } + SparseMatrix.prototype.subset = function (index, replacement, defaultValue) { + // check if it is a pattern matrix [NB: Where is that term defined?] + if (!this._values) { + throw new Error('Cannot invoke subset on a Pattern only matrix') + } + if (isIndex(index)) index = Matrix.parseWithinIndex(index, this._size) // check arguments switch (arguments.length) { diff --git a/src/type/matrix/function/index.js b/src/type/matrix/function/index.js index a5540527f0..3b6071af5c 100644 --- a/src/type/matrix/function/index.js +++ b/src/type/matrix/function/index.js @@ -1,43 +1,70 @@ -import { isBigNumber, isMatrix, isArray } from '../../../utils/is.js' +import { isBigNumber, isMatrix, isArray, isRange } from '../../../utils/is.js' import { factory } from '../../../utils/factory.js' const name = 'index' -const dependencies = ['typed', 'Index'] +const dependencies = ['typed', 'Index', 'number', 'Range'] -export const createIndex = /* #__PURE__ */ factory(name, dependencies, ({ typed, Index }) => { +export const createIndex = /* #__PURE__ */ factory(name, dependencies, ({ typed, Index, number, Range }) => { /** - * Create an index. An Index can store ranges having start, step, and end - * for multiple dimensions. - * Matrix.get, Matrix.set, and math.subset accept an Index as input. + * Create an Index. An Index can store a single position, a range of + * positions, or an arbitrary collection of positions, for each of possibly + * multiple dimensions. This sort of Index can be used to specify some entry + * or entries of a Matrix or Array, in which case the number of dimensions + * of the Index must equal the number of dimensions of the Matrix/Array. + * An Index can also store a single string for accessing a (plain JavaScript) + * object -- even if the object is nested, each layer must be indexed + * separately. + * + * Currently, the only place that Index values are used is in the + * `math.subset()` function (which see) to specify the collection of entries + * to be retrieved or replaced. * * Syntax: * - * math.index(range1, range2, ...) + * math.index(dim1, dim2, ...) * - * Where each range can be any of: + * where each dimension specifier can be any of: * - * - A number - * - A string for getting/setting an object property - * - An instance of `Range` - * - A one-dimensional Array or a Matrix with numbers or booleans + * - A natural number (indicating just the one position that should be used + * along that dimension). + * - A **one**-dimensional Array or Matrix of natural numbers, listing the + * positions that should be used along that dimension. Note that repeated + * positions are supported, although _replacing_ a subset when the index + * has repeated positions may produce somewhat unintuitive results. + * For this option, the specifier will commonly be a Range (a Matrix + * with entries given by start, step, and end values), since often one + * wants a block of consecutive positions. + * - A one-dimensional Array or Matrix of booleans, in which case the + * positions in which `true` values appear are selected. + * - A string. In this case, if the Index is used to access a Matrix or + * Array, the string will be interpreted as a Range specification in + * the form `'start:end'` or `start:step:end`. On the other hand, if it + * is used to access a (plain JavaScript) object, the string value will + * be used directly. * - * Indexes must be zero-based, integer numbers. + * When used via the JavaScript API, the values in an Index are + * interpreted as zero-based positions, and Ranges exclude their endpoints. + * Conversely, when using `math.evaluate()` to compute a value from a string + * expression, Index positions are one-based, and Ranges include their + * endpoints. * * Examples: * * const b = [1, 2, 3, 4, 5] - * math.subset(b, math.index([1, 2, 3])) // returns [2, 3, 4] + * math.subset(b, math.index([1, 2, 3])) // returns [2, 3, 4] ... * math.subset(b, math.index([false, true, true, true, false])) // returns [2, 3, 4] * * const a = math.matrix([[1, 2], [3, 4]]) - * a.subset(math.index(0, 1)) // returns 2 - * a.subset(math.index(0, [false, true])) // returns 2 + * a.subset(math.index(0, 1)) // returns 2 ... + * a.subset(math.index(0, [false, true])) // Matrix [2] + * math.evaluate('subset([1, 2; 3, 4], index(1, 2))') // returns 2 * * See also: * - * bignumber, boolean, complex, matrix, number, string, unit + * subset, range, matrix * - * @param {...*} ranges Zero or more ranges or numbers. + * @param {...*} ranges + * Zero or more numbers, 1-D matrices (often ranges), or strings. * @return {Index} Returns the created index */ return typed(name, { @@ -45,6 +72,14 @@ export const createIndex = /* #__PURE__ */ factory(name, dependencies, ({ typed, const ranges = args.map(function (arg) { if (isBigNumber(arg)) { return arg.toNumber() // convert BigNumber to Number + } else if (isRange(arg)) { + if (arg.datatype() !== 'number') { + return new Range({ + start: number(arg.start), + step: number(arg.step), + length: arg.length + }) + } else return arg } else if (isArray(arg) || isMatrix(arg)) { return arg.map(function (elem) { // convert BigNumber to Number diff --git a/src/type/matrix/function/matrix.js b/src/type/matrix/function/matrix.js index 385301fc3d..e5daec356a 100644 --- a/src/type/matrix/function/matrix.js +++ b/src/type/matrix/function/matrix.js @@ -1,9 +1,11 @@ import { factory } from '../../../utils/factory.js' +import { isRange } from '../../../utils/is.js' const name = 'matrix' -const dependencies = ['typed', 'Matrix', 'DenseMatrix', 'SparseMatrix'] +const dependencies = ['typed', 'Matrix', 'DenseMatrix', 'SparseMatrix', 'Range'] -export const createMatrix = /* #__PURE__ */ factory(name, dependencies, ({ typed, Matrix, DenseMatrix, SparseMatrix }) => { +export const createMatrix = /* #__PURE__ */ factory(name, dependencies, ({ typed, Matrix, DenseMatrix, SparseMatrix, Range }) => { + const rangeCreator = new Range() // dummy to use to get access to .create() /** * Create a Matrix. The function creates a new `math.Matrix` object from * an `Array`. A Matrix has utility functions to manipulate the data in the @@ -22,10 +24,10 @@ export const createMatrix = /* #__PURE__ */ factory(name, dependencies, ({ typed * Examples: * * let m = math.matrix([[1, 2], [3, 4]]) - * m.size() // Array [2, 2] + * m.size() // Array [2, 2] ... * m.resize([3, 2], 5) - * m.valueOf() // Array [[1, 2], [3, 4], [5, 5]] - * m.get([1, 0]) // number 3 + * m.valueOf() // Array [[1, 2], [3, 4], [5, 5]] ... + * m.get([1, 0]) // number 3 * * See also: * @@ -81,6 +83,15 @@ export const createMatrix = /* #__PURE__ */ factory(name, dependencies, ({ typed return new SparseMatrix(data, datatype) } + if (format === 'range') { + if (isRange(data)) { + return new Range({ + start: data.start, step: data.step, length: data.length + }) + } + return rangeCreator.create(data) + } + throw new TypeError('Unknown matrix type ' + JSON.stringify(format) + '.') } }) diff --git a/src/type/matrix/function/sparse.js b/src/type/matrix/function/sparse.js index 05941b8cc1..e599e4da6b 100644 --- a/src/type/matrix/function/sparse.js +++ b/src/type/matrix/function/sparse.js @@ -21,12 +21,13 @@ export const createSparse = /* #__PURE__ */ factory(name, dependencies, ({ typed * Examples: * * let m = math.sparse([[1, 2], [3, 4]]) - * m.size() // Array [2, 2] + * m.size() // Array [2, 2] ... * m.resize([3, 2], 5) - * m.valueOf() // Array [[1, 2], [3, 4], [5, 5]] - * m.get([1, 0]) // number 3 + * m.valueOf() // Array [[1, 2], [3, 4], [5, 5]] ... + * m.get([1, 0]) // number 3 + * * let v = math.sparse([0, 0, 1]) - * v.size() // Array [3, 1] + * v.size() // Array [3, 1] ... * v.get([2, 0]) // number 1 * * See also: diff --git a/src/type/matrix/utils/matAlgo14xDs.js b/src/type/matrix/utils/matAlgo14xDs.js index 342b2b19d9..bd8a773dd4 100644 --- a/src/type/matrix/utils/matAlgo14xDs.js +++ b/src/type/matrix/utils/matAlgo14xDs.js @@ -1,5 +1,5 @@ import { factory } from '../../../utils/factory.js' -import { clone } from '../../../utils/object.js' +import { isDenseMatrix } from '../../../utils/is.js' const name = 'matAlgo14xDs' const dependencies = ['typed'] @@ -21,10 +21,11 @@ export const createMatAlgo14xDs = /* #__PURE__ */ factory(name, dependencies, ({ * https://github.com/josdejong/mathjs/pull/346#issuecomment-97659042 */ return function matAlgo14xDs (a, b, callback, inverse) { + const dense = isDenseMatrix(a) // a arrays - const adata = a._data - const asize = a._size - const adt = a._datatype + const adata = dense ? a._data : a.valueOf() + const asize = dense ? a._size : a.size() + const adt = dense ? a._datatype : a.datatype() // datatype let dt @@ -45,11 +46,7 @@ export const createMatAlgo14xDs = /* #__PURE__ */ factory(name, dependencies, ({ const cdata = asize.length > 0 ? _iterate(cf, 0, asize, asize[0], adata, b, inverse) : [] // c matrix - return a.createDenseMatrix({ - data: cdata, - size: clone(asize), - datatype: dt - }) + return a.create(cdata, dt) } // recursive function diff --git a/src/type/matrix/utils/matrixAlgorithmSuite.js b/src/type/matrix/utils/matrixAlgorithmSuite.js index 216935b20e..3092d98221 100644 --- a/src/type/matrix/utils/matrixAlgorithmSuite.js +++ b/src/type/matrix/utils/matrixAlgorithmSuite.js @@ -5,12 +5,13 @@ import { createMatAlgo14xDs } from './matAlgo14xDs.js' import { broadcast } from './broadcast.js' const name = 'matrixAlgorithmSuite' -const dependencies = ['typed', 'matrix'] +const dependencies = ['typed', 'DenseMatrix'] export const createMatrixAlgorithmSuite = /* #__PURE__ */ factory( - name, dependencies, ({ typed, matrix }) => { + name, dependencies, ({ typed, DenseMatrix }) => { const matAlgo13xDD = createMatAlgo13xDD({ typed }) const matAlgo14xDs = createMatAlgo14xDs({ typed }) + const mat = a => new DenseMatrix(a) /** * Return a signatures object with the usual boilerplate of @@ -38,9 +39,9 @@ export const createMatrixAlgorithmSuite = /* #__PURE__ */ factory( matrixSignatures = { 'DenseMatrix, DenseMatrix': (x, y) => matAlgo13xDD(...broadcast(x, y), elop), 'Array, Array': (x, y) => - matAlgo13xDD(...broadcast(matrix(x), matrix(y)), elop).valueOf(), - 'Array, DenseMatrix': (x, y) => matAlgo13xDD(...broadcast(matrix(x), y), elop), - 'DenseMatrix, Array': (x, y) => matAlgo13xDD(...broadcast(x, matrix(y)), elop) + matAlgo13xDD(...broadcast(mat(x), mat(y)), elop).valueOf(), + 'Array, DenseMatrix': (x, y) => matAlgo13xDD(...broadcast(mat(x), y), elop), + 'DenseMatrix, Array': (x, y) => matAlgo13xDD(...broadcast(x, mat(y)), elop) } // Now incorporate sparse matrices if (options.SS) { @@ -51,13 +52,13 @@ export const createMatrixAlgorithmSuite = /* #__PURE__ */ factory( matrixSignatures['DenseMatrix, SparseMatrix'] = (x, y) => options.DS(...broadcast(x, y), elop, false) matrixSignatures['Array, SparseMatrix'] = - (x, y) => options.DS(...broadcast(matrix(x), y), elop, false) + (x, y) => options.DS(...broadcast(mat(x), y), elop, false) } if (SD) { matrixSignatures['SparseMatrix, DenseMatrix'] = (x, y) => SD(...broadcast(y, x), elop, true) matrixSignatures['SparseMatrix, Array'] = - (x, y) => SD(...broadcast(matrix(y), x), elop, true) + (x, y) => SD(...broadcast(mat(y), x), elop, true) } } else { // No elop, use this @@ -67,13 +68,13 @@ export const createMatrixAlgorithmSuite = /* #__PURE__ */ factory( return matAlgo13xDD(...broadcast(x, y), self) }), 'Array, Array': typed.referToSelf(self => (x, y) => { - return matAlgo13xDD(...broadcast(matrix(x), matrix(y)), self).valueOf() + return matAlgo13xDD(...broadcast(mat(x), mat(y)), self).valueOf() }), 'Array, DenseMatrix': typed.referToSelf(self => (x, y) => { - return matAlgo13xDD(...broadcast(matrix(x), y), self) + return matAlgo13xDD(...broadcast(mat(x), y), self) }), 'DenseMatrix, Array': typed.referToSelf(self => (x, y) => { - return matAlgo13xDD(...broadcast(x, matrix(y)), self) + return matAlgo13xDD(...broadcast(x, mat(y)), self) }) } // Now incorporate sparse matrices @@ -90,7 +91,7 @@ export const createMatrixAlgorithmSuite = /* #__PURE__ */ factory( }) matrixSignatures['Array, SparseMatrix'] = typed.referToSelf(self => (x, y) => { - return options.DS(...broadcast(matrix(x), y), self, false) + return options.DS(...broadcast(mat(x), y), self, false) }) } if (SD) { @@ -100,7 +101,7 @@ export const createMatrixAlgorithmSuite = /* #__PURE__ */ factory( }) matrixSignatures['SparseMatrix, Array'] = typed.referToSelf(self => (x, y) => { - return SD(...broadcast(matrix(y), x), self, true) + return SD(...broadcast(mat(y), x), self, true) }) } } @@ -115,9 +116,9 @@ export const createMatrixAlgorithmSuite = /* #__PURE__ */ factory( matrixSignatures[scalar + ', DenseMatrix'] = (x, y) => matAlgo14xDs(y, x, elop, true) matrixSignatures['Array,' + scalar] = - (x, y) => matAlgo14xDs(matrix(x), y, elop, false).valueOf() + (x, y) => matAlgo14xDs(mat(x), y, elop, false).valueOf() matrixSignatures[scalar + ', Array'] = - (x, y) => matAlgo14xDs(matrix(y), x, elop, true).valueOf() + (x, y) => matAlgo14xDs(mat(y), x, elop, true).valueOf() } else { matrixSignatures['DenseMatrix,' + scalar] = typed.referToSelf(self => (x, y) => { @@ -129,11 +130,11 @@ export const createMatrixAlgorithmSuite = /* #__PURE__ */ factory( }) matrixSignatures['Array,' + scalar] = typed.referToSelf(self => (x, y) => { - return matAlgo14xDs(matrix(x), y, self, false).valueOf() + return matAlgo14xDs(mat(x), y, self, false).valueOf() }) matrixSignatures[scalar + ', Array'] = typed.referToSelf(self => (x, y) => { - return matAlgo14xDs(matrix(y), x, self, true).valueOf() + return matAlgo14xDs(mat(y), x, self, true).valueOf() }) } } diff --git a/src/type/unit/Unit.js b/src/type/unit/Unit.js index daebd92a87..89d6c60821 100644 --- a/src/type/unit/Unit.js +++ b/src/type/unit/Unit.js @@ -13,6 +13,7 @@ const dependencies = [ 'subtractScalar', 'multiplyScalar', 'divideScalar', + 'oneUnitless', 'pow', 'abs', 'fix', @@ -33,6 +34,7 @@ export const createUnitClass = /* #__PURE__ */ factory(name, dependencies, ({ subtractScalar, multiplyScalar, divideScalar, + oneUnitless, pow, abs, fix, @@ -45,6 +47,7 @@ export const createUnitClass = /* #__PURE__ */ factory(name, dependencies, ({ BigNumber, Fraction }) => { + const one = oneUnitless const toNumber = number const fixPrefixDefault = false const skipAutomaticSimplificationDefault = true @@ -624,6 +627,17 @@ export const createUnitClass = /* #__PURE__ */ factory(name, dependencies, ({ return true } + /** + * Returns true if this unit instance is unitless, i.e., a number with + * no dimension. + * + * @memberof Unit + * @return {boolean} true if unitless + */ + Unit.prototype.unitless = function () { + return this.equalBase(Unit.BASE_UNITS.NONE) + } + /** * Check if this unit equals another unit * @memberof Unit @@ -776,22 +790,6 @@ export const createUnitClass = /* #__PURE__ */ factory(name, dependencies, ({ } } - /** - * Create a value one with the numeric type of `typeOfValue`. - * For example, `one(new BigNumber(3))` returns `BigNumber(1)` - * @param {number | Fraction | BigNumber} typeOfValue - * @returns {number | Fraction | BigNumber} - */ - function one (typeOfValue) { - // TODO: this is a workaround to prevent the following BigNumber conversion error from throwing: - // "TypeError: Cannot implicitly convert a number with >15 significant digits to BigNumber" - // see https://github.com/josdejong/mathjs/issues/3450 - // https://github.com/josdejong/mathjs/pull/3375 - const convert = Unit._getNumberConverter(typeOf(typeOfValue)) - - return convert(1) - } - /** * Calculate the absolute value of a unit * @memberof Unit diff --git a/src/type/unit/function/unit.js b/src/type/unit/function/unit.js index bae05a6b79..49018901ce 100644 --- a/src/type/unit/function/unit.js +++ b/src/type/unit/function/unit.js @@ -19,12 +19,17 @@ export const createUnitFunction = /* #__PURE__ */ factory(name, dependencies, ({ * * Examples: * - * const kph = math.unit('km/h') // returns Unit km/h (valueless) - * const v = math.unit(25, kph) // returns Unit 25 km/h - * const a = math.unit(5, 'cm') // returns Unit 50 mm - * const b = math.unit('23 kg') // returns Unit 23 kg + * // A valueless unit: + * const kph = math.unit('km/h') + * kph // returns Unit km/h ... + * math.unit(25, kph) // returns Unit 25 km/h + * + * const a = math.unit(5, 'cm') + * a // returns Unit 5 cm ... * a.to('m') // returns Unit 0.05 m * + * math.unit('23 kg') // returns Unit 23 kg + * * See also: * * bignumber, boolean, complex, index, matrix, number, string, createUnit diff --git a/src/utils/collection.js b/src/utils/collection.js index ecdd6a30ad..7b6f7a12c6 100644 --- a/src/utils/collection.js +++ b/src/utils/collection.js @@ -176,3 +176,31 @@ export function scatter (a, j, w, x, u, mark, cindex, f, inverse, update, value) } } } + +/** + * Simple parser for the `start[:step]:end` notation + * used for ranges and the like, when the full parser is not being + * used. Produces a plain object with properties 'start', 'step', + * 'end'. The property values are returned as strings, since they may be + * converted into numeric values differently in different locations. + * Missing parts are represented by the empty string. + * + * @param {string} notation to be parsed + * @return {object} fields found, or null if the notation was unparseable + */ +export function parseRange (str) { + const args = str.split(':') + const retval = { start: args[0], step: '' } + switch (args.length) { + case 2: + retval.end = args[1] + return retval + case 3: + // Empty _middle_ part is a syntax error; only start/end can be elided + if (args[1] === '') return null + retval.step = args[1] + retval.end = args[2] + return retval + } + return null +} diff --git a/src/utils/product.js b/src/utils/product.js index d3e0fa5bdf..9d9a6ffa84 100644 --- a/src/utils/product.js +++ b/src/utils/product.js @@ -1,16 +1,14 @@ -/** @param {number} i - * @param {number} n - * @returns {number} product of i to n +/** @param {number | bigint} i + * @param {number | bigint} n [NB: same type as i] + * @returns {number | bigint} product of i to n */ export function product (i, n) { - if (n < i) { - return 1 - } + if (i <= 0) return i - i // always 0, for number or bigint + // Which is faster: `const one = typeof i === 'number' ? 1 : 1n`, or: + const one = i / i // always has the proper type + if (n < i) return one + if (n === i) return n - if (n === i) { - return n - } - - const half = (n + i) >> 1 // divide (n + i) by 2 and truncate to integer - return product(i, half) * product(half + 1, n) + const half = (n + i) >> one // divide (n + i) by 2 and truncate to integer + return product(i, half) * product(half + one, n) } diff --git a/src/utils/snapshot.js b/src/utils/snapshot.js index f058da2946..bcc799acda 100644 --- a/src/utils/snapshot.js +++ b/src/utils/snapshot.js @@ -155,6 +155,7 @@ export function createSnapshotFromFactories (factories) { 'multiplyScalar', 'print', 'divideScalar', + 'oneUnitless', 'parse', 'compile', 'parser', diff --git a/test/benchmark/factorial.js b/test/benchmark/factorial.js index 19437d6d80..56352ed913 100644 --- a/test/benchmark/factorial.js +++ b/test/benchmark/factorial.js @@ -1,5 +1,6 @@ import BigNumber from 'decimal.js' import { Bench } from 'tinybench' +import { prod, Range } from '../../lib/esm/index.js' import { formatTaskResult } from './utils/formatTaskResult.js' const results = [] @@ -41,51 +42,122 @@ function betterFactorial (n) { return prod } +function splitFactorial (m, n) { + if (n - m < 9) { + let product = new BigNumber(m) + while (++m <= n) product = product.mul(new BigNumber(m)) + return product + } + const split = Math.floor((m + n) / 2) + return splitFactorial(m, split).mul(splitFactorial(split + 1, n)) +} + +function prodFactorial (n) { + return prod(new Range({ start: new BigNumber(1), length: n })) +} + const bench = new Bench({ time: 100, iterations: 100 }) - .add('bigFactorial for small numbers', function () { + .add('bigFactorial for 8', function () { const res = bigFactorial(new BigNumber(8)) results.push(res) }) - .add('new bigFactorial for small numbers', function () { + .add('new bigFactorial for 8', function () { const res = betterFactorial(new BigNumber(8)) results.push(res) }) + .add('split factorial for 8', function () { + const res = splitFactorial(1, 8) + results.push(res) + }) + .add('prod range for 8', function () { + const res = prodFactorial(8) + results.push(res) + }) - .add('bigFactorial for small numbers 2', function () { + .add('bigFactorial for 20', function () { const res = bigFactorial(new BigNumber(20)) results.push(res) }) - .add('new bigFactorial for small numbers 2', function () { + .add('new bigFactorial for 20', function () { const res = betterFactorial(new BigNumber(20)) results.push(res) }) + .add('split factorial for 20', function () { + const res = splitFactorial(1, 20) + results.push(res) + }) + .add('prod range for 20', function () { + const res = prodFactorial(20) + results.push(res) + }) - .add('bigFactorial for big numbers', function () { + .add('bigFactorial for 600', function () { const res = bigFactorial(new BigNumber(600)) results.push(res) }) - .add('new bigFactorial for big numbers', function () { + .add('new bigFactorial for 600', function () { const res = betterFactorial(new BigNumber(600)) results.push(res) }) + .add('split factorial for 600', function () { + const res = splitFactorial(1, 600) + results.push(res) + }) + .add('prod range for 600', function () { + const res = prodFactorial(600) + results.push(res) + }) - .add('bigFactorial for HUGE numbers', function () { + .add('bigFactorial for 1.5K', function () { const res = bigFactorial(new BigNumber(1500)) results.push(res) }) - .add('new bigFactorial for HUGE numbers', function () { + .add('new bigFactorial for 1.5K', function () { const res = betterFactorial(new BigNumber(1500)) results.push(res) }) + .add('split factorial for 1.5K', function () { + const res = splitFactorial(1, 1500) + results.push(res) + }) + .add('prod range for 1.5K', function () { + const res = prodFactorial(1500) + results.push(res) + }) - .add('bigFactorial for "HUGER" numbers', function () { + .add('bigFactorial for 10K', function () { const res = bigFactorial(new BigNumber(10000)) results.push(res) }) - .add('new bigFactorial for "HUGER" numbers', function () { + .add('new bigFactorial for 10K', function () { const res = betterFactorial(new BigNumber(10000)) results.push(res) }) + .add('split factorial for 10K', function () { + const res = splitFactorial(1, 10000) + results.push(res) + }) + .add('prod range for 10K', function () { + const res = prodFactorial(10000) + results.push(res) + }) + + .add('bigFactorial for 30K', function () { + const res = bigFactorial(new BigNumber(30000)) + results.push(res) + }) + .add('new bigFactorial for 30K', function () { + const res = betterFactorial(new BigNumber(30000)) + results.push(res) + }) + .add('split factorial for 30K', function () { + const res = splitFactorial(1, 30000) + results.push(res) + }) + .add('prod range for 30K', function () { + const res = prodFactorial(30000) + results.push(res) + }) bench.addEventListener('cycle', (event) => console.log(formatTaskResult(bench, event.task))) await bench.run() diff --git a/test/node-tests/browser.test.cjs b/test/node-tests/browser.test.cjs index 67906a26e2..537758b594 100644 --- a/test/node-tests/browser.test.cjs +++ b/test/node-tests/browser.test.cjs @@ -99,7 +99,8 @@ describe('lib/browser', function () { 'compile', 'parse', 'parser', // TODO: add embedded docs for compile, parse, and parser? 'reviver', 'replacer', // TODO: add embedded docs for reviver and replacer? 'apply', // FIXME: apply is not supported right now because of security concerns - 'addScalar', 'subtractScalar', 'divideScalar', 'multiplyScalar', 'equalScalar' + 'addScalar', 'subtractScalar', 'divideScalar', 'multiplyScalar', 'equalScalar', + 'oneUnitless' ] // test whether all functions are documented diff --git a/test/node-tests/doc.test.js b/test/node-tests/doc.test.js index 01e3ee8fd5..da250d95f0 100644 --- a/test/node-tests/doc.test.js +++ b/test/node-tests/doc.test.js @@ -40,7 +40,7 @@ function extractValue (spec) { } const keywords = { number: 'Number(_)', - BigNumber: 'math.bignumber(_)', + BigNumber: "math.bignumber('_')", Fraction: 'math.fraction(_)', Complex: "math.complex('_')", Unit: "math.unit('_')", @@ -92,10 +92,10 @@ const ignoreFunctions = new Set([ ]) const knownProblems = new Set([ - 'setUnion', 'unequal', 'equal', 'deepEqual', 'compareNatural', 'randomInt', + 'unequal', 'equal', 'deepEqual', 'compareNatural', 'randomInt', 'random', 'pickRandom', 'kldivergence', 'parser', 'compile', 're', 'im', - 'subset', 'squeeze', 'rotationMatrix', + 'rotationMatrix', 'rotate', 'reshape', 'partitionSelect', 'matrixFromFunction', 'matrixFromColumns', 'getMatrixDataType', 'eigs', 'diff', 'nthRoots', 'nthRoot', @@ -104,7 +104,6 @@ const knownProblems = new Set([ 'rationalize', 'qr', 'lusolve', 'lup', 'derivative', 'symbolicEqual', 'schur', 'sylvester', 'freqz', 'round', 'import', 'typed', - 'unit', 'sparse', 'matrix', 'index', 'bignumber', 'fraction', 'complex', 'parse' ]) @@ -175,6 +174,7 @@ function checkExpectation (want, got) { const OKundocumented = new Set([ 'apply', // deprecated backwards-compatibility synonym of mapSlices 'addScalar', 'subtractScalar', 'divideScalar', 'multiplyScalar', 'equalScalar', + 'oneUnitless', 'docs', 'FibonacciHeap', 'IndexError', 'DimensionError', 'ArgumentsError' ]) @@ -410,8 +410,13 @@ describe('Testing examples from (jsdoc) comments', function () { if (accumulation) { accumulation += '\n' } accumulation += parts[0] } + let resetAccumulation = true if (accumulation !== '' && expectation === undefined) { expectationFrom = parts[1] + if (expectationFrom.endsWith('...')) { + expectationFrom = expectationFrom.slice(0, -3).trimEnd() + resetAccumulation = false + } expectation = extractExpectation(expectationFrom) parts[1] = '' } @@ -425,7 +430,7 @@ describe('Testing examples from (jsdoc) comments', function () { } maybeCheckExpectation( doc.name, expectation, expectationFrom, value, accumulation) - accumulation = '' + if (resetAccumulation) accumulation = '' } expectationFrom = parts[1] expectation = extractExpectation(expectationFrom, 'requireSignal') diff --git a/test/node-tests/treeShaking/treeShaking.test.js b/test/node-tests/treeShaking/treeShaking.test.js index 9d2ba40cc8..35cc94abfb 100644 --- a/test/node-tests/treeShaking/treeShaking.test.js +++ b/test/node-tests/treeShaking/treeShaking.test.js @@ -63,12 +63,17 @@ describe('tree shaking', function () { } // Test whether the size is small enough - // At this moment, the full library size is 559137 bytes (unzipped), - // and the size of this tree-shaken bundle is 98494 bytes (unzipped) - // this may grow or shrink in the future + // When this was first written, the full library size wass 559137 bytes + // (unzipped), and the size of this tree-shaken bundle was 98494 bytes + // (unzipped). These values generally grow slowly as features are added + // to the library; they can sometimes shrink if the code is made more + // efficient. But in general, a slight increase in the tree-shaken + // bundle is not a problem, and the maxSize can be bumped up to + // accommodate. But a sudden jump in size indicates a tree-shaking/ + // bundling problem. assert.strictEqual(info.assets[0].name, bundleName) const size = info.assets[0].size - const maxSize = 135000 + const maxSize = 137000 assert(size < maxSize, 'bundled size must be small enough ' + '(actual size: ' + size + ' bytes, max size: ' + maxSize + ' bytes)') diff --git a/test/typescript-tests/testTypes.ts b/test/typescript-tests/testTypes.ts index 4b38e7710c..6f9116c373 100644 --- a/test/typescript-tests/testTypes.ts +++ b/test/typescript-tests/testTypes.ts @@ -85,15 +85,20 @@ Basic usage examples math.sqrt(-4) math.pow(m2by2, 2) + // @ts-expect-error: since one(number) returns 1, this comparison must fail + if (math.one(3.5) === 2) { + console.error('This should not happen') + } + // @ts-expect-error: since zero(bigint) returns 0n, this comparison fails + if (math.zero(-23n) === 1n) { + console.error('Nor should this happen') + } const angle = 0.2 math.add(math.pow(math.sin(angle), 2), math.pow(math.cos(angle), 2)) math.add(2, 3, 4) math.add(2, 3, math.bignumber(4)) - // @ts-expect-error: string arguments are not supported by the types, but it works (if the string contains a number) math.add(2, '3') - // @ts-expect-error: string arguments are not supported by the types, but it works (if the string contains a number), but should throw an error if it is something else assert.throws(() => math.add(2, '3 + 5')) - // @ts-expect-error: string arguments are not supported by the types, but it works (if the string contains a number), but should throw an error if it is something else assert.throws(() => math.add(2, '3 cm')) // @ts-expect-error: no arguments are not supported by the types, and should throw an error assert.throws(() => math.add()) @@ -102,11 +107,8 @@ Basic usage examples math.multiply(2, 3, 4) math.multiply(2, 3, math.bignumber(4)) - // @ts-expect-error: string arguments are not supported by the types, but it works (if the string contains a number) math.multiply(2, '2') // currently not supported by the types, but turns out to work - // @ts-expect-error: string arguments are not supported by the types, but it works (if the string contains a number), but should throw an error if it is something else assert.throws(() => math.multiply(2, '3 + 5')) - // @ts-expect-error: string arguments are not supported by the types, but it works (if the string contains a number), but should throw an error if it is something else assert.throws(() => math.multiply(2, '3 cm')) // @ts-expect-error: no arguments are not supported by the types, and should throw an error assert.throws(() => math.multiply()) @@ -782,6 +784,17 @@ Chaining examples .dotDivide(2) ).toMatchTypeOf>() + // scalarDivide + expectTypeOf(math.chain(1).scalarDivide(2)).toMatchTypeOf< + MathJsChain + >() + expectTypeOf(math.chain([1, 2, 3]).scalarDivide(3)).toMatchTypeOf< + MathJsChain + >() + expectTypeOf(math.chain([2, 5, 6]).scalarDivide([1, 2, 3])).toMatchTypeOf< + MathJsChain + >() + // dotMultiply expectTypeOf(math.chain(1).dotMultiply(2)).toMatchTypeOf< MathJsChain @@ -997,7 +1010,7 @@ Chaining examples MathJsChain >() expectTypeOf(math.chain([1, Infinity]).isFinite()).toMatchTypeOf< - MathJsChain + MathJsChain> >() // bernoulli @@ -1399,8 +1412,8 @@ Matrices examples // create matrices and arrays. a matrix is just a wrapper around an Array, // providing some handy utilities. - const a: Matrix = math.matrix([1, 4, 9, 16, 25]) - const b: Matrix = math.matrix(math.ones([2, 3])) + const a: Matrix = math.matrix([1, 4, 9, 16, 25]) + const b: Matrix = math.ones(math.matrix([2, 3])) b.size() // @ts-expect-error ... ones() in a chain cannot take more dimensions @@ -1444,17 +1457,17 @@ Matrices examples (i: number[]) => math.fraction(i[0], i[1] + 1), 'dense' ) - const j: number[][] = math.matrixFromRows( + const j: Matrix = math.matrixFromRows( [1, 2, 3], math.matrix([[4], [5], [6]]) ) - assert.strictEqual(j[1][2], 6) + assert.strictEqual(j.get([1, 2]), 6) const _k: Matrix = math.matrixFromRows(f, math.row(h, 1)) - const l: number[][] = math.matrixFromColumns( + const l: Matrix = math.matrixFromColumns( [1, 2, 3], math.matrix([[4], [5], [6]]) ) - assert.strictEqual(l[2][1], 6) + assert.strictEqual(l.get([2, 1]), 6) const _m: Matrix = math.matrixFromColumns(f, math.row(h, 1)) } @@ -1679,8 +1692,8 @@ Math types examples: Type results after multiplying 'MathTypes' with matrices { const math = create(all, {}) - const abc: MathArray = [1, 2, 3, 4] - const bcd: MathArray = [ + const abc = [1, 2, 3, 4] + const bcd = [ [1, 2, 3, 4], [2, 3, 4, 5], [4, 5, 6, 7], @@ -1694,7 +1707,7 @@ Math types examples: Type results after multiplying 'MathTypes' with matrices const hij: number[][] = [[1], [2], [3], [4]] const ijk: number[][] = [[1, 2, 3, 4]] - const Mbcd = math.matrix(bcd) + const Mbcd: Matrix = math.matrix(bcd) const Mabc = math.matrix(abc) // Number @@ -1894,6 +1907,8 @@ Units examples math.multiply(b, 2) math.divide(math.unit('1 m'), math.unit('1 s')) math.pow(math.unit('12 in'), 3) + math.one(math.unit('5m')) + math.zero(math.unit('22 m/secs^2')) // units can be converted to a specific type, or to a number b.to('cm') @@ -3014,7 +3029,7 @@ Statistics functions' return types ], 1 ) - ).toMatchTypeOf() + ).toMatchTypeOf>() expectTypeOf(math.max(1, 2, 3)).toMatchTypeOf() expectTypeOf(math.max([1, 2, 3])).toMatchTypeOf() @@ -3061,23 +3076,27 @@ Statistics functions' return types number | BigNumber | bigint | Fraction | Complex | Unit >() - expectTypeOf(math.quantileSeq([1, 2, 3], 0.75)).toMatchTypeOf() + expectTypeOf(math.quantileSeq([1, 2, 3], 0.75)).toMatchTypeOf< + number | MathCollection + >() expectTypeOf(math.quantileSeq([1, 2, 3, 4, 5], [0.25, 0.75])).toMatchTypeOf< - MathArray | MathScalarType + MathCollection >() expectTypeOf( math.quantileSeq([1, 2, 3, 4, 5], [0.25, 0.75]) as number[] ).toMatchTypeOf() - expectTypeOf(math.quantileSeq([[1, 2, 3]], 0.75)).toMatchTypeOf() - expectTypeOf( - math.quantileSeq([math.bignumber('123')], 0.75) - ).toMatchTypeOf() + expectTypeOf(math.quantileSeq([[1, 2, 3]], 0.75)).toMatchTypeOf< + number | MathCollection + >() + expectTypeOf(math.quantileSeq([math.bignumber('123')], 0.75)).toMatchTypeOf< + BigNumber | MathCollection + >() expectTypeOf(math.quantileSeq(math.matrix([1, 2, 3]), 0.75)).toMatchTypeOf< - MathScalarType | MathArray + number | MathCollection >() expectTypeOf( math.quantileSeq([math.unit('5cm'), math.unit('10cm')], 0.75) - ).toMatchTypeOf() + ).toMatchTypeOf>() } /* diff --git a/test/unit-tests/core/typed.test.js b/test/unit-tests/core/typed.test.js index 39908b3c3b..6184db2529 100644 --- a/test/unit-tests/core/typed.test.js +++ b/test/unit-tests/core/typed.test.js @@ -370,7 +370,7 @@ describe('typed', function () { assert.strictEqual(math.isChain(), false) }) - it('should convert a bigint to number if possible', function () { + it('should convert a bigint/Fraction to number if possible', function () { const double = math.typed('double', { number: (x) => x + x }) @@ -378,6 +378,7 @@ describe('typed', function () { assert.strictEqual(double(2), 4) assert.strictEqual(double(2n), 4) assert.throws(() => double(12345678901234567890n), /value exceeds the max safe integer/) + assert.strictEqual(double(math.fraction(5, 2)), 5) }) it('should convert a bigint to BigNumber', function () { @@ -390,12 +391,14 @@ describe('typed', function () { assert.deepStrictEqual(double(12345678901234567890n), math.bignumber('24691357802469135780')) }) - it('should convert a bigint to Fraction', function () { + it('should convert a bigint or number to Fraction', function () { + const frac4 = math.fraction(4) const double = math.typed('double', { Fraction: (x) => x.add(x) }) - assert.deepStrictEqual(double(math.fraction(2)), math.fraction(4)) - assert.deepStrictEqual(double(2n), math.fraction(4)) + assert.deepStrictEqual(double(math.fraction(2)), frac4) + assert.deepStrictEqual(double(2n), frac4) + assert.deepStrictEqual(double(2), frac4) }) }) diff --git a/test/unit-tests/expression/function/help.test.js b/test/unit-tests/expression/function/help.test.js index adc65100ec..2d12933abd 100644 --- a/test/unit-tests/expression/function/help.test.js +++ b/test/unit-tests/expression/function/help.test.js @@ -5,8 +5,9 @@ import { embeddedDocs } from '../../../../src/expression/embeddedDocs/embeddedDo let mathDocs = math.create(math.all) const originalConfig = mathDocs.config() // Add names to the skipDocs array if they are not meant to have embedded docs -const skipDocs = new Set(['import', 'addScalar', 'divideScalar', 'equalScalar', 'multiplyScalar', - 'subtractScalar', 'apply', 'replacer', 'reviver']) +const skipDocs = new Set([ + 'import', 'addScalar', 'divideScalar', 'equalScalar', 'multiplyScalar', + 'subtractScalar', 'oneUnitless', 'apply', 'replacer', 'reviver']) // Add names to skipExamples if their examples in the embedded docs contain acceptable errors const skipExamples = new Set([]) diff --git a/test/unit-tests/expression/node/RangeNode.test.js b/test/unit-tests/expression/node/RangeNode.test.js index 97e7b2056d..808b3e60dd 100644 --- a/test/unit-tests/expression/node/RangeNode.test.js +++ b/test/unit-tests/expression/node/RangeNode.test.js @@ -6,6 +6,7 @@ const Node = math.Node const ConstantNode = math.ConstantNode const SymbolNode = math.SymbolNode const RangeNode = math.RangeNode +const Range = math.Range const OperatorNode = math.OperatorNode describe('RangeNode', function () { @@ -50,7 +51,8 @@ describe('RangeNode', function () { const n = new RangeNode(start, end, step) const expr = n.compile() - assert.deepStrictEqual(expr.evaluate(), math.matrix([0, 2, 4, 6, 8, 10])) + assert.deepStrictEqual( + expr.evaluate(), new Range({ start: 0, step: 2, last: 10 })) }) it('should filter a RangeNode', function () { diff --git a/test/unit-tests/expression/parse.test.js b/test/unit-tests/expression/parse.test.js index 444247244f..eef4d81612 100644 --- a/test/unit-tests/expression/parse.test.js +++ b/test/unit-tests/expression/parse.test.js @@ -174,8 +174,10 @@ describe('parse', function () { }) it('should spread a range over multiple lines', function () { - assert.deepStrictEqual(parse('2:\n4').compile().evaluate(), math.matrix([2, 3, 4])) - assert.deepStrictEqual(parse('2:\n2:\n6').compile().evaluate(), math.matrix([2, 4, 6])) + assert.deepStrictEqual( + parse('2:\n4').compile().evaluate(), math.range(2, 5)) + assert.deepStrictEqual( + parse('2:\n2:\n6').compile().evaluate(), math.range(2, 7, 2)) }) it('should spread an index over multiple lines', function () { @@ -850,9 +852,9 @@ describe('parse', function () { assert.deepStrictEqual(parseAndEval('c=concat([[1,2]], [[3,4]], 1)', scope), math.matrix([[1, 2], [3, 4]])) assert.deepStrictEqual(parseAndEval('c=concat([[1,2]], [[3,4]], 2)', scope), math.matrix([[1, 2, 3, 4]])) assert.deepStrictEqual(parseAndEval('c=concat([[1]], [2;3], 1)', scope), math.matrix([[1], [2], [3]])) - assert.deepStrictEqual(parseAndEval('d=1:3', scope), math.matrix([1, 2, 3])) + assert.deepStrictEqual(parseAndEval('d=1:3', scope), math.range(1, 4)) assert.deepStrictEqual(parseAndEval('concat(d,d)', scope), math.matrix([1, 2, 3, 1, 2, 3])) - assert.deepStrictEqual(parseAndEval('e=1+d', scope), math.matrix([2, 3, 4])) + assert.deepStrictEqual(parseAndEval('e=1+d', scope), math.range(2, 5)) assert.deepStrictEqual(parseAndEval('size(e)', scope), [3]) assert.deepStrictEqual(parseAndEval('concat(e,e)', scope), math.matrix([2, 3, 4, 2, 3, 4])) assert.deepStrictEqual(parseAndEval('[[],[]]', scope), math.matrix([[], []])) @@ -888,6 +890,19 @@ describe('parse', function () { const scope = {} assert.throws(function () { parseAndEval('c=concat(a, [1,2,3])', scope) }) }) + + it( + 'should interpret comma-separated expressions in parentheses as arrays', + function () { + assert.deepStrictEqual(parseAndEval('(3,4,5)'), [3, 4, 5]) + assert.deepStrictEqual(parseAndEval('(5,12,13,)'), [5, 12, 13]) + assert.deepStrictEqual(parseAndEval('(5,)'), [5]) + assert.deepStrictEqual(parseAndEval('()'), []) + assert.strictEqual(parseAndEval('(7,24,25)[2]'), 24) + assert.deepStrictEqual(parseAndEval('size((8, 15, 17))'), [3]) + assert.deepStrictEqual( + parseAndEval('((),(0,),(0,1))'), [[], [0], [0, 1]]) + }) }) describe('objects', function () { @@ -2213,17 +2228,17 @@ describe('parse', function () { it('should parse : (range)', function () { assert.ok(parseAndEval('2:5') instanceof Matrix) - assert.deepStrictEqual(parseAndEval('2:5'), math.matrix([2, 3, 4, 5])) - assert.deepStrictEqual(parseAndEval('10:-2:0'), math.matrix([10, 8, 6, 4, 2, 0])) - assert.deepStrictEqual(parseAndEval('2:4.0'), math.matrix([2, 3, 4])) - assert.deepStrictEqual(parseAndEval('2:4.5'), math.matrix([2, 3, 4])) - assert.deepStrictEqual(parseAndEval('2:4.1'), math.matrix([2, 3, 4])) - assert.deepStrictEqual(parseAndEval('2:3.9'), math.matrix([2, 3])) - assert.deepStrictEqual(parseAndEval('2:3.5'), math.matrix([2, 3])) - assert.deepStrictEqual(parseAndEval('3:-1:0.5'), math.matrix([3, 2, 1])) - assert.deepStrictEqual(parseAndEval('3:-1:0.5'), math.matrix([3, 2, 1])) - assert.deepStrictEqual(parseAndEval('3:-1:0.1'), math.matrix([3, 2, 1])) - assert.deepStrictEqual(parseAndEval('3:-1:-0.1'), math.matrix([3, 2, 1, 0])) + assert.deepStrictEqual(parseAndEval('2:5'), math.range(2, 6)) + assert.deepStrictEqual( + parseAndEval('10:-2:0'), math.range(10, 0, -2, true)) + assert.deepStrictEqual(parseAndEval('2:4.0'), math.range(2, 5)) + assert.deepStrictEqual(parseAndEval('2:4.5'), math.range(2, 5)) + assert.deepStrictEqual(parseAndEval('2:4.1'), math.range(2, 5)) + assert.deepStrictEqual(parseAndEval('2:3.9'), math.range(2, 4)) + assert.deepStrictEqual(parseAndEval('2:3.5'), math.range(2, 4)) + assert.deepStrictEqual(parseAndEval('3:-1:0.5'), math.range(3, 0, -1)) + assert.deepStrictEqual(parseAndEval('3:-1:0.1'), math.range(3, 0, -1)) + assert.deepStrictEqual(parseAndEval('3:-1:-0.1'), math.range(3, -1, -1)) }) it('should parse to', function () { @@ -2358,7 +2373,7 @@ describe('parse', function () { assert.deepStrictEqual(parseAndEval('3 ? true : false; 22'), new ResultSet([22])) assert.deepStrictEqual(parseAndEval('3 ? 5cm to m : 5cm in mm'), new Unit(5, 'cm').to('m')) assert.deepStrictEqual(parseAndEval('2 == 4-2 ? [1,2] : false'), math.matrix([1, 2])) - assert.deepStrictEqual(parseAndEval('false ? 1:2:6'), math.matrix([2, 3, 4, 5, 6])) + assert.deepStrictEqual(parseAndEval('false ? 1:2:6'), math.range(2, 7)) }) it('should respect precedence between left/right shift and relational operators', function () { @@ -2518,10 +2533,13 @@ describe('parse', function () { }) it('should create a range from bignumbers', function () { - assert.deepStrictEqual(bigmath.evaluate('4:6'), - bigmath.matrix([new BigNumber(4), new BigNumber(5), new BigNumber(6)])) - assert.deepStrictEqual(bigmath.evaluate('0:2:4'), - bigmath.matrix([new BigNumber(0), new BigNumber(2), new BigNumber(4)])) + const four = new BigNumber(4) + assert.deepStrictEqual( + bigmath.evaluate('4:6'), + bigmath.range(four, new BigNumber(6), true)) + assert.deepStrictEqual( + bigmath.evaluate('0:2:4'), + bigmath.range(new BigNumber(0), four, new BigNumber(2), true)) }) it('should create a matrix with bignumbers', function () { diff --git a/test/unit-tests/expression/security.test.js b/test/unit-tests/expression/security.test.js index 6ea71ffce4..7b1f8e3886 100644 --- a/test/unit-tests/expression/security.test.js +++ b/test/unit-tests/expression/security.test.js @@ -214,7 +214,7 @@ describe('security', function () { 'i={isIndex:true,isScalar:f,size:g,min:h,max:h,dimension:k};' + 'f=subset(subset([[[0]]],i),index(1,1,1))("console.log(\'hacked...\')");' + 'f()') - }, /TypeError: Unexpected type of argument in function subset \(expected: Index, actual: Object, index: 1\)/) + }, /TypeError: Unexpected type of argument in function subset/) }) it('should not allow accessing proto via dimension', function () { @@ -248,9 +248,17 @@ describe('security', function () { it('should not allow replacing validateSafeMethod with a local variant', function () { assert.throws(function () { math.evaluate("evaluate(\"f(validateSafeMethod)=cos.constructor(\\\"return evaluate\\\")()\")(evaluate(\"f(x,y)=0\"))(\"console.log('hacked...')\")") - }, /SyntaxError: Value expected/) + }, /TypeError: Unexpected type of argument/) }) + it( + 'should not allow replacing validateSafeMethod with a local variant (2)', + function () { + assert.throws(function () { + math.evaluate("evaluate(\"f(validateSafeMethod)=cos.constructor(\\\"return evaluate\\\")()\")?.(evaluate(\"f(x,y)=0\"))(\"console.log('hacked...')\")") + }, /No access to method .constructor/) + }) + it('should not allow abusing toString', function () { assert.throws(function () { math.evaluate("badToString = evaluate(\"f() = 1\"); badReplace = evaluate(\"f(a, b) = \\\"evaluate\\\"\"); badNumber = {toString:badToString, replace:badReplace}; badNode = {\"isNode\": true, \"type\": \"ConstantNode\", \"valueType\": \"number\", \"value\": badNumber}; x = evaluate(\"f(child, path, parent) = badNode\", {badNode:badNode}); parse(\"(1)\").map(x).compile().evaluate()(\"console.log('hacked...')\")") @@ -260,13 +268,13 @@ describe('security', function () { it('should not allow creating a bad FunctionAssignmentNode', function () { assert.throws(function () { math.evaluate("badNode={isNode:true,type:\"FunctionAssignmentNode\",expr:parse(\"1\"),types:{join:evaluate(\"f(a)=\\\"\\\"\")},params:{\"forEach\":evaluate(\"f(x)=1\"),\"join\":evaluate(\"f(x)=\\\"){return evaluate;}});return fn;})())}});return fn;})());}};//\\\"\")}};parse(\"f()=x\").map(evaluate(\"f(a,b,c)=badNode\",{\"badNode\":badNode})).compile().evaluate()()()(\"console.log('hacked...')\")") - }, /SyntaxError: Value expected/) + }, /TypeError: Callback function must return a Node/) }) it('should not allow creating a bad OperatorNode (1)', function () { assert.throws(function () { math.evaluate("badNode={isNode:true,type:\"FunctionAssignmentNode\",expr:parse(\"1\"),types:{join:evaluate(\"f(a)=\\\"\\\"\")},params:{\"forEach\":evaluate(\"f(x)=1\"),\"join\":evaluate(\"f(x)=\\\"){return evaluate;}});return fn;})())}});return fn;})());}};//\\\"\")}};parse(\"f()=x\").map(evaluate(\"f(a,b,c)=badNode\",{\"badNode\":badNode})).compile().evaluate()()()(\"console.log('hacked...')\")") - }, /SyntaxError: Value expected/) + }, /TypeError: Callback function must return a Node/) }) it('should not allow creating a bad OperatorNode (2)', function () { @@ -316,7 +324,7 @@ describe('security', function () { 'evilMath=x.create().done();' + 'evilMath.import({"_compile":f(a,b,c)="evaluate","isNode":f()=true}); ' + "parse(\"(1)\").map(g(a,b,c)=evilMath.chain()).compile().evaluate()(\"console.log('hacked...')\")") - }, /SyntaxError: Value expected/) + }, /Error: Undefined symbol Chain/) }) it('should not allow passing a function name containg bad contents', function () { diff --git a/test/unit-tests/expression/transform/subset.transform.test.js b/test/unit-tests/expression/transform/subset.transform.test.js new file mode 100644 index 0000000000..6c2c800b0f --- /dev/null +++ b/test/unit-tests/expression/transform/subset.transform.test.js @@ -0,0 +1,16 @@ +import assert from 'assert' +import math from '../../../../src/defaultInstance.js' + +const ev = math.evaluate +const matrix = math.matrix +const nummat = x => matrix(x, 'dense', 'number') + +describe('subset.transform', function () { + it('should obey indexing conventions of expressions', function () { + assert.strictEqual(ev('subset([1,2;3,4], (2, 1))'), 3) + assert.deepStrictEqual( + ev('subset(range(2,10), ([9,7,5],))'), nummat([10, 8, 6])) + assert.deepStrictEqual( + ev("subset([1,2,3;4,5,6], ('1:2', '2:3'))"), matrix([[2, 3], [5, 6]])) + }) +}) diff --git a/test/unit-tests/function/arithmetic/add.test.js b/test/unit-tests/function/arithmetic/add.test.js index aa5c385bf3..fdd6b39244 100644 --- a/test/unit-tests/function/arithmetic/add.test.js +++ b/test/unit-tests/function/arithmetic/add.test.js @@ -248,6 +248,19 @@ describe('add', function () { }) }) + describe('Range', function () { + it('should keep the result a Range when possible', function () { + assert.deepStrictEqual(add(math.range(2, 5), 7), new math.Range(9, 12)) + assert.deepStrictEqual( + add(-3, math.range(6, 11, 2)), new math.Range(3, 8, 2)) + assert.deepStrictEqual( + add(math.range(0.5, 4.5), math.range(0, 2.1, 0.5)), + new math.Range({ start: 0.5, step: 1.5, length: 4 })) + assert.deepStrictEqual( + add(math.range(2, 5), [1, 3, 6]), math.matrix([3, 6, 10])) + }) + }) + describe('multiple arguments', function () { it('should add more than two arguments', function () { assert.deepStrictEqual(add(2, 3, 4), 9) diff --git a/test/unit-tests/function/arithmetic/divide.test.js b/test/unit-tests/function/arithmetic/divide.test.js index d14353d03e..370bc95683 100644 --- a/test/unit-tests/function/arithmetic/divide.test.js +++ b/test/unit-tests/function/arithmetic/divide.test.js @@ -210,6 +210,14 @@ describe('divide', function () { assert.throws(function () { divide(a, [[1]]) }) }) + describe('Range', function () { + it('should keep the result a Range when possible', function () { + assert.deepStrictEqual( + divide(math.range(2, 5), 7), + new math.Range({ start: 2 / 7, step: 1 / 7, length: 3 })) + }) + }) + /* // These are supported now --ericman314 it('should throw an error if dividing a number by a unit', function() { diff --git a/test/unit-tests/function/arithmetic/dotDivide.test.js b/test/unit-tests/function/arithmetic/dotDivide.test.js index e99fa1cde9..aa22fcea86 100644 --- a/test/unit-tests/function/arithmetic/dotDivide.test.js +++ b/test/unit-tests/function/arithmetic/dotDivide.test.js @@ -24,7 +24,7 @@ describe('dotDivide', function () { assert.ok(isNaN(dotDivide(false, false))) }) - it('should add mixed numbers and booleans', function () { + it('should divide mixed numbers and booleans', function () { assert.strictEqual(dotDivide(2, true), 2) assert.strictEqual(dotDivide(2, false), Infinity) approxEqual(dotDivide(true, 2), 0.5) diff --git a/test/unit-tests/function/arithmetic/log.test.js b/test/unit-tests/function/arithmetic/log.test.js index 3ad86017b2..5d7f26e58a 100644 --- a/test/unit-tests/function/arithmetic/log.test.js +++ b/test/unit-tests/function/arithmetic/log.test.js @@ -1,7 +1,7 @@ // test log import assert from 'assert' -import { approxDeepEqual } from '../../../../tools/approx.js' +import { approxEqual, approxDeepEqual } from '../../../../tools/approx.js' import math from '../../../../src/defaultInstance.js' const mathPredictable = math.create({ predictable: true }) const complex = math.complex @@ -96,9 +96,7 @@ describe('log', function () { it('should return the log of a Fraction', function () { approxDeepEqual(log(fraction(27, 8), fraction(9, 4)), fraction(3, 2)) - assert.throws(() => log(fraction(27, 8), fraction(-2, 5)), - /Cannot implicitly convert a Fraction to BigNumber or vice versa/ - ) + approxEqual(log(fraction(27, 8), fraction(2, 5)), -1.32752114804928) }) it('should handle complex number with large imaginary part', function () { diff --git a/test/unit-tests/function/arithmetic/multiply.test.js b/test/unit-tests/function/arithmetic/multiply.test.js index d25420481e..e21e068e16 100644 --- a/test/unit-tests/function/arithmetic/multiply.test.js +++ b/test/unit-tests/function/arithmetic/multiply.test.js @@ -899,6 +899,18 @@ describe('multiply', function () { }) }) + describe('Range', function () { + it('should keep the result a Range when possible', function () { + assert.deepStrictEqual( + multiply(math.range(2, 5), 7), + new math.Range({ start: 14, step: 7, length: 3 })) + const frac = math.fraction + assert.deepStrictEqual( + multiply(frac(1, 3), math.range(2n, 5n)), + new math.Range(frac(2, 3), frac(5, 3), frac(1, 3))) + }) + }) + describe('multiple arguments', function () { it('should multiply more than two arguments', function () { assert.deepStrictEqual(multiply(2, 3, 4), 24) diff --git a/test/unit-tests/function/arithmetic/one.test.js b/test/unit-tests/function/arithmetic/one.test.js new file mode 100644 index 0000000000..a546c92fcf --- /dev/null +++ b/test/unit-tests/function/arithmetic/one.test.js @@ -0,0 +1,18 @@ +// test one +import assert from 'assert' + +import math from '../../../../src/defaultInstance.js' +const one = math.one + +describe('one', function () { + it('should return multiplicative identity', function () { + assert.strictEqual(one(Infinity), 1) + assert.deepStrictEqual(one(math.bignumber(7)), math.bignumber(1)) + assert.strictEqual(one(-17n), 1n) + assert.deepStrictEqual(one(math.complex(0, 1)), math.complex(1)) + assert.strictEqual(one(false), true) + assert.deepStrictEqual(one(math.fraction(3, 10)), math.fraction(1)) + assert.deepStrictEqual(one(math.zeros(3, 3)), math.identity(3)) + assert.throws(() => one(math.zeros(5))) + }) +}) diff --git a/test/unit-tests/function/arithmetic/round.test.js b/test/unit-tests/function/arithmetic/round.test.js index 30b03ee21a..deeeb51bf0 100644 --- a/test/unit-tests/function/arithmetic/round.test.js +++ b/test/unit-tests/function/arithmetic/round.test.js @@ -173,7 +173,7 @@ describe('round', function () { }) it('should round each element in a matrix, array, range', function () { - assert.deepStrictEqual(round(math.range(0, 2.1, 1 / 3), 2), math.matrix([0, 0.33, 0.67, 1, 1.33, 1.67, 2])) + assert.deepStrictEqual(round(math.range(0, 2.1, 1 / 3), 2), math.matrix([0, 0.33, 0.67, 1, 1.33, 1.67, 2], 'dense', 'number')) assert.deepStrictEqual(round(math.range(0, 2.1, 1 / 3)), math.matrix([0, 0, 1, 1, 1, 2, 2])) assert.deepStrictEqual(round([1.7, 2.3]), [2, 2]) assert.deepStrictEqual(round(math.matrix([1.7, 2.3])).valueOf(), [2, 2]) diff --git a/test/unit-tests/function/arithmetic/scalarDivide.test.js b/test/unit-tests/function/arithmetic/scalarDivide.test.js new file mode 100644 index 0000000000..d26fcc5e5f --- /dev/null +++ b/test/unit-tests/function/arithmetic/scalarDivide.test.js @@ -0,0 +1,92 @@ +// test scalarDivide (check if an element is a scalar multiple of another) +import assert from 'assert' + +import math from '../../../../src/defaultInstance.js' +const sD = math.scalarDivide +const C = math.complex +const frac = math.fraction +const Big = math.bignumber +const M = math.matrix + +describe('scalarDivide', function () { + it('should determine when one scalar is a multiple of another', function () { + assert.strictEqual(sD(4, 2), 2) + assert.strictEqual(sD(-4, 2), -2) + assert.strictEqual(sD(4, -2), -2) + assert.strictEqual(sD(-4, -2), 2) + assert.strictEqual(sD(4, 0), undefined) + assert.strictEqual(sD(0, -5), 0) + assert.strictEqual(sD(0, 0), 0) + assert.strictEqual(sD(true, true), 1) + assert.strictEqual(sD(true, false), undefined) + assert.strictEqual(sD(false, true), 0) + assert.strictEqual(sD(false, false), false) + assert.strictEqual(sD(2, true), 2) + assert.strictEqual(sD(2, false), undefined) + assert.strictEqual(sD(null, 1), undefined) + assert.deepStrictEqual(sD(C(2, 3), 2), C(1, 1.5)) + assert.deepStrictEqual(sD(C(2, 3), C(0, 4)), C(0.75, -0.5)) + assert.strictEqual(sD(C(0, 2), C(0, 4)), 0.5) + assert.deepStrictEqual(sD(5, C(1, 2)), C(1, -2)) + assert.deepStrictEqual(sD(math.unit('5m'), 10), math.unit('0.5m')) + const result = math.unit('2m^-1') + result.skipAutomaticSimplification = false + assert.deepStrictEqual(sD(10, math.unit('5m')), result) + assert.strictEqual(sD(math.unit('10m'), math.unit('5m')), 2) + assert.strictEqual(sD(math.unit(10), 5), 2) + assert.strictEqual(sD(4n, 2n), 2n) + assert.deepStrictEqual(sD(5n, 3n), frac(5, 3)) + assert.deepStrictEqual(sD(frac(4), frac(2)), frac(2)) + assert.deepStrictEqual(sD(frac(5, 4), frac(3, 4)), frac(5, 3)) + assert.deepStrictEqual(sD(Big(4), 2), Big(2)) + assert.deepStrictEqual(sD(Big(1.44), Big(1.2)), Big(1.2)) + assert.strictEqual(sD(Big(0.3), 0), undefined) + }) + + it('should error with incorrect arguments', function () { + assert.throws(() => sD(2, 3, 4)) + assert.throws(() => sD(2)) + }) + + it('should determine when arrays are scalar multiples', function () { + assert.strictEqual(sD([2, 4, 6], [1, 2, 3]), 2) + assert.strictEqual(sD([[0.5, 1], [1.5, 2]], [[1, 2], [3, 4]]), 0.5) + assert.strictEqual(sD([], []), 0) + assert.strictEqual(sD([2, 4, 7], [1, 2, 3]), undefined) + assert.strictEqual(sD([[0.5, 1], [1.5, 2]], [[1, 2], [3, 3]]), undefined) + assert.strictEqual(sD([], [0]), undefined) + assert.strictEqual(sD([0], [0]), 0) + assert.strictEqual(sD([0, 5], [0, 2]), 2.5) + assert.strictEqual(sD([0, 0, 5], [0, 2, 1]), undefined) + assert.strictEqual(sD([0, 5, 1], [0, 0, 1]), undefined) + }) + + it('should never relate scalars and arrays', function () { + assert.strictEqual(sD(4, [1, 2, 3]), undefined) + assert.strictEqual(sD([1, 2, 3], 4), undefined) + }) + + it('should determine when matrices are scalar multiples', function () { + assert.strictEqual(sD(M([2, 4, 6]), M([1, 2, 3])), 2) + assert.strictEqual(sD(M([[0.5, 1], [1.5, 2]]), M([[1, 2], [3, 4]])), 0.5) + assert.strictEqual(sD(M([]), M([])), 0) + assert.strictEqual(sD(M([2, 4, 7]), M([1, 2, 3])), undefined) + assert.strictEqual( + sD(M([[0.5, 1], [1.5, 2]]), M([[1, 2], [3, 3]])), undefined) + assert.strictEqual(sD(M([]), M([0])), undefined) + assert.strictEqual(sD(M([0]), M([0])), 0) + assert.strictEqual(sD(M([0, 5]), M([0, 2])), 2.5) + assert.strictEqual(sD(M([0, 0, 5]), M([0, 2, 1])), undefined) + assert.strictEqual(sD(M([0, 5, 1]), M([0, 0, 1])), undefined) + }) + + it('should never relate scalars and arrays', function () { + assert.strictEqual(sD(4, [1, 2, 3]), undefined) + assert.strictEqual(sD([1, 2, 3], 4), undefined) + }) + + it('should never relate scalars and matrices', function () { + assert.strictEqual(sD(4, M([1, 2, 3])), undefined) + assert.strictEqual(sD(M([1, 2, 3]), 4), undefined) + }) +}) diff --git a/test/unit-tests/function/arithmetic/subtract.test.js b/test/unit-tests/function/arithmetic/subtract.test.js index 1302032171..dd10d6b791 100644 --- a/test/unit-tests/function/arithmetic/subtract.test.js +++ b/test/unit-tests/function/arithmetic/subtract.test.js @@ -245,6 +245,20 @@ describe('subtract', function () { }) }) + describe('Range', function () { + it('should keep the result a Range when possible', function () { + assert.deepStrictEqual( + subtract(math.range(2, 5), 7), new math.Range(-5, -2)) + assert.deepStrictEqual( + subtract(-3, math.range(6, 11, 2)), new math.Range(-9, -14, -2)) + assert.deepStrictEqual( + subtract(math.range(0.5, 4.5), math.range(0, 2.1, 0.5)), + new math.Range({ start: 0.5, step: 0.5, length: 4 })) + assert.deepStrictEqual( + subtract(math.range(2, 5), [1, 3, 6]), math.matrix([1, 0, -2])) + }) + }) + it('should throw an error in case of invalid number of arguments', function () { assert.throws(function () { subtract(1) }, /TypeError: Too few arguments/) assert.throws(function () { subtract(1, 2, 3) }, /TypeError: Too many arguments/) diff --git a/test/unit-tests/function/arithmetic/zero.test.js b/test/unit-tests/function/arithmetic/zero.test.js new file mode 100644 index 0000000000..d098026888 --- /dev/null +++ b/test/unit-tests/function/arithmetic/zero.test.js @@ -0,0 +1,20 @@ +// test zero +import assert from 'assert' + +import math from '../../../../src/defaultInstance.js' +const zero = math.zero + +describe('zero', function () { + it('should return additive identity', function () { + assert.strictEqual(zero(-Infinity), 0) + assert.deepStrictEqual(zero(math.bignumber(7)), math.bignumber(0)) + assert.strictEqual(zero(117n), 0n) + assert.deepStrictEqual(zero(math.complex(0, 1)), math.complex(0)) + assert.strictEqual(zero(false), false) + assert.deepStrictEqual(zero(math.fraction(3, 10)), math.fraction(0)) + assert.deepStrictEqual(zero(math.unit(3, 'm')), math.unit(0, 'm')) + assert.deepStrictEqual( + zero(math.identity(3, 3)), math.zeros(math.matrix([3, 3]))) + assert.deepStrictEqual(zero([1, 2, 3]), math.zeros([3])) + }) +}) diff --git a/test/unit-tests/function/matrix/concat.test.js b/test/unit-tests/function/matrix/concat.test.js index d27aa2e4af..c3a7d57ce3 100644 --- a/test/unit-tests/function/matrix/concat.test.js +++ b/test/unit-tests/function/matrix/concat.test.js @@ -79,6 +79,11 @@ describe('concat', function () { ]) }) + it('should keep Ranges if possible', function () { + assert.deepStrictEqual( + math.concat(math.range(2, 10), math.range(10, 20)), new math.Range(2, 20)) + }) + it('should concatenate strings', function () { assert.strictEqual(math.concat('a', 'b'), 'ab') assert.strictEqual(math.concat('a', 'b', 'c'), 'abc') diff --git a/test/unit-tests/function/matrix/matrixFrom.test.js b/test/unit-tests/function/matrix/matrixFrom.test.js index dd4fa080ca..da07bb5800 100644 --- a/test/unit-tests/function/matrix/matrixFrom.test.js +++ b/test/unit-tests/function/matrix/matrixFrom.test.js @@ -62,9 +62,9 @@ describe('matrixFrom...', function () { actual = math.matrixFromRows(matrix([[1, 2, 3]], 'sparse'), matrix([[4], [5], [6]], 'sparse'), matrix([[7, 8, 9]], 'sparse')) assert.deepStrictEqual(actual, matrix(expected)) - // for a mixed type, returns an array + // for a mixed type, returns an Matrix actual = math.matrixFromRows([1, 2, 3], [4, 5, 6], matrix([7, 8, 9])) - assert.deepStrictEqual(actual, expected) + assert.deepStrictEqual(actual, matrix(expected)) }) it('...Columns', function () { @@ -91,8 +91,8 @@ describe('matrixFrom...', function () { actual = math.matrixFromColumns(matrix([[1, 2, 3]], 'sparse'), matrix([[4], [5], [6]], 'sparse'), matrix([[7, 8, 9]], 'sparse')) assert.deepStrictEqual(actual, matrix(expected)) - // for a mixed type, returns an array + // for a mixed type, returns a Matrix actual = math.matrixFromColumns([1, 2, 3], [4, 5, 6], matrix([7, 8, 9])) - assert.deepStrictEqual(actual, expected) + assert.deepStrictEqual(actual, matrix(expected)) }) }) diff --git a/test/unit-tests/function/matrix/ones.test.js b/test/unit-tests/function/matrix/ones.test.js index 3485a109c4..f8e34a43af 100644 --- a/test/unit-tests/function/matrix/ones.test.js +++ b/test/unit-tests/function/matrix/ones.test.js @@ -44,6 +44,19 @@ describe('ones', function () { assert.deepStrictEqual(ones([three]), [one, one, one]) }) + it('should create a Range of all-one vectors', function () { + const oneRange = ones(2, 3, 'range') + assert.strictEqual(oneRange.length, 2) + assert.strictEqual(oneRange.step, 0) + assert.deepStrictEqual( + oneRange.start, new math.Range({ start: 1, step: 0, length: 3 })) + assert.strictEqual(oneRange.get([1, 1]), 1) + assert.deepStrictEqual(oneRange.toArray(), [[1, 1, 1], [1, 1, 1]]) + assert.strictEqual( + oneRange.toString(), + 'Range{start: Range{start: 1, step: 0, length: 3}, step: 0, length: 2}') + }) + it('should create a 3D matrix with ones', function () { const res = [ [ diff --git a/test/unit-tests/function/matrix/range.test.js b/test/unit-tests/function/matrix/range.test.js index ac9eff92e5..eb01fb89a9 100644 --- a/test/unit-tests/function/matrix/range.test.js +++ b/test/unit-tests/function/matrix/range.test.js @@ -1,61 +1,73 @@ import assert from 'assert' import math from '../../../../src/defaultInstance.js' const range = math.range -const matrix = math.matrix +const Range = math.Range const bignumber = math.bignumber const unit = math.unit const evaluate = math.evaluate describe('range', function () { it('should parse a valid string correctly', function () { - assert.deepStrictEqual(range('1:6'), matrix([1, 2, 3, 4, 5])) - assert.deepStrictEqual(range('0:2:10'), matrix([0, 2, 4, 6, 8])) - assert.deepStrictEqual(range('5:-1:0'), matrix([5, 4, 3, 2, 1])) - assert.deepStrictEqual(range('2:-2:-3'), matrix([2, 0, -2])) + assert.deepStrictEqual(range('1:6'), new Range(1, 6)) + assert.deepStrictEqual(range('0:2:10'), new Range(0, 10, 2)) + assert.deepStrictEqual(range('5:-1:0'), new Range(5, 0, -1)) + assert.deepStrictEqual(range('2:-2:-3'), new Range(2, -4, -2)) }) it('should throw an error in case of invalid string', function () { - assert.throws(function () { range('1:2:6:4') }, /is no valid range/) - assert.throws(function () { range('1') }, /is no valid range/) - assert.throws(function () { range('1,3:4') }, /is no valid range/) - assert.throws(function () { range('1:2,4') }, /is no valid range/) - assert.throws(function () { range('1:a') }, /is no valid range/) + assert.throws(function () { range('1:2:6:4') }, SyntaxError) + assert.throws(function () { range('1') }, SyntaxError) + assert.throws(function () { range('1,3:4') }, /Error: Cannot convert/) + assert.throws(function () { range('1:2,4') }, /Error: Cannot convert/) + assert.throws(function () { range('1:a') }, /Error: Cannot convert/) }) it('should create a range start:1:end if called with 2 numbers', function () { - assert.deepStrictEqual(range(3, 6), matrix([3, 4, 5])) - assert.deepStrictEqual(range(1, 6), matrix([1, 2, 3, 4, 5])) - assert.deepStrictEqual(range(1, 6.1), matrix([1, 2, 3, 4, 5, 6])) - assert.deepStrictEqual(range(1, 5.9), matrix([1, 2, 3, 4, 5])) - assert.deepStrictEqual(range(6, 1), matrix([])) + assert.deepStrictEqual(range(3, 6), new Range(3, 6)) + assert.deepStrictEqual(range(3, 6).valueOf(), [3, 4, 5]) + assert.deepStrictEqual(range(1, 6).valueOf(), [1, 2, 3, 4, 5]) + assert.deepStrictEqual(range(1, 6.1).valueOf(), [1, 2, 3, 4, 5, 6]) + assert.deepStrictEqual(range(1, 5.9).valueOf(), [1, 2, 3, 4, 5]) + assert.deepStrictEqual(range(6, 1).valueOf(), []) }) it('should create a range start:step:end if called with 3 numbers', function () { - assert.deepStrictEqual(range(0, 10, 2), matrix([0, 2, 4, 6, 8])) - assert.deepStrictEqual(range(5, 0, -1), matrix([5, 4, 3, 2, 1])) - assert.deepStrictEqual(range(2, -4, -2), matrix([2, 0, -2])) + assert.deepStrictEqual(range(0, 10, 2), new Range(0, 10, 2)) + assert.deepStrictEqual(range(0, 10, 2).valueOf(), [0, 2, 4, 6, 8]) + assert.deepStrictEqual(range(5, 0, -1).valueOf(), [5, 4, 3, 2, 1]) + assert.deepStrictEqual(range(2, -4, -2).valueOf(), [2, 0, -2]) }) - it('should throw an error when step==0', function () { - assert.throws(function () { range(0, 0, 0) }, /Step must be non-zero/) - assert.throws(function () { range(0, 10, 0) }, /Step must be non-zero/) - assert.throws(function () { range(0, 10, 0, true) }, /Step must be non-zero/) + it('should take care handling step==0', function () { + assert.deepStrictEqual(range(0, 0, 0).valueOf(), []) + assert.throws(function () { range(0, 10, 0) }, /No scalar/) + assert.throws(function () { range(0, 10, 0, true) }, /No scalar/) }) it('should create an empty range when start and stop are equal', function () { - assert.deepStrictEqual(range(0, 0), matrix([])) - assert.deepStrictEqual(range(1, 1, 2), matrix([])) - assert.deepStrictEqual(range('0:0'), matrix([])) - assert.deepStrictEqual(range('0:1:0'), matrix([])) - assert.deepStrictEqual(range('1:2:1'), matrix([])) - assert.deepStrictEqual(range('1:1:1'), matrix([])) + assert.deepStrictEqual(range(0, 0).valueOf(), []) + assert.deepStrictEqual(range(1, 1, 2).valueOf(), []) + assert.deepStrictEqual(range('0:0').valueOf(), []) + assert.deepStrictEqual(range('0:1:0').valueOf(), []) + assert.deepStrictEqual(range('1:2:1').valueOf(), []) + assert.deepStrictEqual(range('1:1:1').valueOf(), []) }) it('should create an array with the end value when start and stop are equal and includeEnd=true', function () { - assert.deepStrictEqual(range(0, 0, true), matrix([0])) - assert.deepStrictEqual(range(1, 1, 2, true), matrix([1])) - assert.deepStrictEqual(range('0:0', true), matrix([0])) - assert.deepStrictEqual(range('1:1:1', true), matrix([1])) + assert.deepStrictEqual(range(0, 0, true).valueOf(), [0]) + assert.deepStrictEqual(range(1, 1, 2, true).valueOf(), [1]) + assert.deepStrictEqual(range('0:0', true).valueOf(), [0]) + assert.deepStrictEqual(range('1:1:1', true).valueOf(), [1]) + }) + + it('should accept an object of attributes', function () { + const frac = math.fraction + assert.deepStrictEqual(range({ start: 0, last: 3 }).valueOf(), [0, 1, 2, 3]) + assert.deepStrictEqual( + range({ start: 0n, end: 8n, step: 2n }).valueOf(), [0n, 2n, 4n, 6n]) + assert.deepStrictEqual( + range({ start: frac(1, 2), length: 3 }).valueOf(), + [frac(1, 2), frac(3, 2), frac(5, 2)]) }) it('should output an array when setting matrix==="array"', function () { @@ -68,135 +80,178 @@ describe('range', function () { }) it('should create a range with bigints', function () { - assert.deepStrictEqual(range(1n, 3n), matrix([1n, 2n])) - assert.deepStrictEqual(range(3n, 1n, -1n), matrix([3n, 2n])) - assert.deepStrictEqual(range(1n, 3n, true), matrix([1n, 2n, 3n])) - assert.deepStrictEqual(range(3n, 1n, -1n, true), matrix([3n, 2n, 1n])) + assert.deepStrictEqual(range(1n, 3n), new Range(1n, 3n)) + assert.deepStrictEqual(range(1n, 3n).valueOf(), [1n, 2n]) + assert.deepStrictEqual(range(3n, 1n, -1n).valueOf(), [3n, 2n]) + assert.deepStrictEqual(range(1n, 3n, true).valueOf(), [1n, 2n, 3n]) + assert.deepStrictEqual(range(3n, 1n, -1n, true).valueOf(), [3n, 2n, 1n]) }) it('should handle mixed numbers and bigints appropriately', function () { - assert.deepStrictEqual(range(1n, 3), matrix([1n, 2n])) - assert.deepStrictEqual(range(3, 1n, -1n), matrix([3n, 2n])) - assert.deepStrictEqual(range(3n, 1, -1), matrix([3n, 2n])) - assert.deepStrictEqual(range(1, 3n, true), matrix([1n, 2n, 3n])) - assert.deepStrictEqual(range(3n, 1, -1n, true), matrix([3n, 2n, 1n])) - assert.deepStrictEqual(range(3, 1n, -1, true), matrix([3n, 2n, 1n])) - assert.deepStrictEqual(range(1, 5, 2n), matrix([1, 3])) - assert.deepStrictEqual(range(5, 1, -2n, true), matrix([5, 3, 1])) + assert.deepStrictEqual(range(1n, 3), new Range(1n, 3n)) + assert.deepStrictEqual(range(1n, 3).valueOf(), [1n, 2n]) + assert.deepStrictEqual(range(3, 1n, -1n).valueOf(), [3, 2]) + assert.deepStrictEqual(range(3n, 1, -1).valueOf(), [3, 2]) + assert.deepStrictEqual(range(1, 3n, true).valueOf(), [1, 2, 3]) + assert.deepStrictEqual(range(3n, 1, -1n, true).valueOf(), [3n, 2n, 1n]) + assert.deepStrictEqual(range(3, 1n, -1, true).valueOf(), [3, 2, 1]) + assert.deepStrictEqual(range(1, 5, 2n).valueOf(), [1, 3]) + assert.deepStrictEqual(range(5, 1, -2n, true).valueOf(), [5, 3, 1]) }) it('should create a range with bignumbers', function () { - assert.deepStrictEqual(range(bignumber(1), bignumber(3)), matrix([bignumber(1), bignumber(2)])) - assert.deepStrictEqual(range(bignumber(3), bignumber(1), bignumber(-1)), matrix([bignumber(3), bignumber(2)])) + const bigRange = range(bignumber(1), bignumber(3)) + assert.deepStrictEqual(bigRange, new Range(bignumber(1), bignumber(3))) + assert.deepStrictEqual(bigRange.valueOf(), [bignumber(1), bignumber(2)]) + assert.deepStrictEqual( + range(bignumber(3), bignumber(1), bignumber(-1)).valueOf(), + [bignumber(3), bignumber(2)]) }) it('should throw an error from bignumbers when step==0', function () { - assert.throws(function () { range(bignumber(0), bignumber(10), bignumber(0)) }, /Step must be non-zero/) - assert.throws(function () { range(bignumber(0), bignumber(10), bignumber(0), true) }, /Step must be non-zero/) + assert.throws(function () { range(bignumber(0), bignumber(10), bignumber(0)) }, /No scalar/) + assert.throws(function () { range(bignumber(0), bignumber(10), bignumber(0), true) }, /No scalar/) }) it('should create a range with mixed numbers and bignumbers', function () { - assert.deepStrictEqual(range(bignumber(1), 3), matrix([bignumber(1), bignumber(2)])) - assert.deepStrictEqual(range(1, bignumber(3)), matrix([bignumber(1), bignumber(2)])) - - assert.deepStrictEqual(range(1, bignumber(3), bignumber(1)), matrix([bignumber(1), bignumber(2)])) - assert.deepStrictEqual(range(bignumber(1), 3, bignumber(1)), matrix([bignumber(1), bignumber(2)])) - assert.deepStrictEqual(range(bignumber(1), bignumber(3), 1), matrix([bignumber(1), bignumber(2)])) - - assert.deepStrictEqual(range(bignumber(1), 3, 1), matrix([bignumber(1), bignumber(2)])) - assert.deepStrictEqual(range(1, bignumber(3), 1), matrix([bignumber(1), bignumber(2)])) - assert.deepStrictEqual(range(1, 3, bignumber(1)), matrix([bignumber(1), bignumber(2)])) + assert.deepStrictEqual( + range(bignumber(1), 3).valueOf(), [bignumber(1), bignumber(2)]) + assert.deepStrictEqual(range(1, bignumber(3)).valueOf(), [1, 2]) + + assert.deepStrictEqual( + range(1, bignumber(3), bignumber(1)).valueOf(), + [bignumber(1), bignumber(2)]) + assert.deepStrictEqual( + range(bignumber(1), 3, bignumber(1)).valueOf(), + [bignumber(1), bignumber(2)]) + assert.deepStrictEqual( + range(bignumber(1), bignumber(3), 1).valueOf(), + [bignumber(1), bignumber(2)]) + + assert.deepStrictEqual( + range(bignumber(1), 3, 1).valueOf(), [bignumber(1), bignumber(2)]) + assert.deepStrictEqual(range(1, bignumber(3), 1).valueOf(), [1, 2]) + assert.deepStrictEqual( + range(1, 3, bignumber(1)).valueOf(), [bignumber(1), bignumber(2)]) }) - it('should parse a range with bignumbers', function () { + it('should interpret strings as numbers regardless', function () { const bigmath = math.create({ number: 'BigNumber' }) - const bignumber = bigmath.bignumber - const matrix = bigmath.matrix - assert.deepStrictEqual(bigmath.range('1:3'), matrix([bignumber(1), bignumber(2)])) - assert.deepStrictEqual(bigmath.range('3:-1:0'), matrix([bignumber(3), bignumber(2), bignumber(1)])) + assert.deepStrictEqual(bigmath.range('1:3').valueOf(), [1, 2]) + assert.deepStrictEqual(bigmath.range('3:-1:0').valueOf(), [3, 2, 1]) }) it('should throw an error when parsing a an invalid string to a bignumber range', function () { const bigmath = math.create({ number: 'BigNumber' }) - assert.throws(function () { bigmath.range('1:a') }, /is no valid range/) + assert.throws(function () { bigmath.range('1:a') }, /Error: Cannot convert/) }) it('should create a range with units', function () { - assert.deepStrictEqual(range(unit(1, 'm'), unit(3, 'm'), unit(1, 'm')), matrix([unit(1, 'm'), unit(2, 'm')])) - assert.deepStrictEqual(range(unit(3, 'm'), unit(1, 'm'), unit(-1, 'm')), matrix([unit(3, 'm'), unit(2, 'm')])) + assert.deepStrictEqual( + range(unit(1, 'm'), unit(3, 'm'), unit(1, 'm')).valueOf(), + [unit(1, 'm'), unit(2, 'm')]) + assert.deepStrictEqual( + range(unit(3, 'm'), unit(1, 'm'), unit(-1, 'm')).valueOf(), + [unit(3, 'm'), unit(2, 'm')]) }) it('should parse a range with units', function () { - assert.deepStrictEqual(evaluate('1m:1m:3m'), matrix([unit(1, 'm'), unit(2, 'm'), unit(3, 'm')])) - assert.deepStrictEqual(evaluate('3m:-1m:0m'), matrix([unit(3, 'm'), unit(2, 'm'), unit(1, 'm'), unit(0, 'm')])) - assert.deepStrictEqual(evaluate('range(1m,3m,1m)'), matrix([unit(1, 'm'), unit(2, 'm'), unit(3, 'm')])) - assert.deepStrictEqual(evaluate('range(3m,0m,-1m)'), matrix([unit(3, 'm'), unit(2, 'm'), unit(1, 'm'), unit(0, 'm')])) + assert.deepStrictEqual( + evaluate('1m:1m:3m').valueOf(), + [unit(1, 'm'), unit(2, 'm'), unit(3, 'm')]) + assert.deepStrictEqual( + evaluate('3m:-1m:0m').valueOf(), + [unit(3, 'm'), unit(2, 'm'), unit(1, 'm'), unit(0, 'm')]) + assert.deepStrictEqual( + evaluate('range(1m,3m,1m)').valueOf(), + [unit(1, 'm'), unit(2, 'm'), unit(3, 'm')]) + assert.deepStrictEqual( + evaluate('range(3m,0m,-1m)').valueOf(), + [unit(3, 'm'), unit(2, 'm'), unit(1, 'm'), unit(0, 'm')]) }) it('should gracefully handle round-off errors', function () { - assert.deepStrictEqual(range(1, 2, 0.1, true)._size, [11]) - assert.deepStrictEqual(range(0.1, 0.2, 0.01, true)._size, [11]) - assert.deepStrictEqual(range(1, 5, 0.1)._size, [40]) - assert.deepStrictEqual(range(2, 1, -0.1, true)._size, [11]) - assert.deepStrictEqual(range(5, 1, -0.1)._size, [40]) - assert.deepStrictEqual(range(-3.2909135802469143, 3.2909135802469143, (3.2909135802469143 + 3.2909135802469143) / 10, true)._size, [11]) - assert.deepStrictEqual(range(-3.2909135802469143, 3.2909135802469143, (3.2909135802469143 + 3.2909135802469143) / 9, true)._size, [10]) - assert.deepStrictEqual(range(-3.2909135802469143, 3.2909135802469143, (3.2909135802469143 + 3.2909135802469143) / 10)._size, [10]) - assert.deepStrictEqual(range(-3.2909135802469143, 3.2909135802469143, (3.2909135802469143 + 3.2909135802469143) / 9)._size, [9]) + assert.deepStrictEqual(range(1, 2, 0.1, true).size(), [11]) + assert.deepStrictEqual(range(0.1, 0.2, 0.01, true).size(), [11]) + assert.deepStrictEqual(range(1, 5, 0.1).size(), [40]) + assert.deepStrictEqual(range(2, 1, -0.1, true).size(), [11]) + assert.deepStrictEqual(range(5, 1, -0.1).size(), [40]) + assert.deepStrictEqual(range( + -3.2909135802469143, + 3.2909135802469143, + (3.2909135802469143 + 3.2909135802469143) / 10, + true).size(), [11]) + assert.deepStrictEqual(range( + -3.2909135802469143, + 3.2909135802469143, + (3.2909135802469143 + 3.2909135802469143) / 9, + true).size(), [10]) + assert.deepStrictEqual(range( + -3.2909135802469143, + 3.2909135802469143, + (3.2909135802469143 + 3.2909135802469143) / 10).size(), [10]) + assert.deepStrictEqual(range( + -3.2909135802469143, + 3.2909135802469143, + (3.2909135802469143 + 3.2909135802469143) / 9).size(), [9]) }) describe('option includeEnd', function () { it('should parse a string and include end', function () { - assert.deepStrictEqual(range('1:6', false), matrix([1, 2, 3, 4, 5])) - assert.deepStrictEqual(range('1:2:6', false), matrix([1, 3, 5])) - assert.deepStrictEqual(range('1:6', true), matrix([1, 2, 3, 4, 5, 6])) + assert.deepStrictEqual(range('1:6', false).valueOf(), [1, 2, 3, 4, 5]) + assert.deepStrictEqual(range('1:2:6', false).valueOf(), [1, 3, 5]) + assert.deepStrictEqual(range('1:6', true).valueOf(), [1, 2, 3, 4, 5, 6]) }) it('should create a range start:1:end and include end', function () { - assert.deepStrictEqual(range(3, 6, false), matrix([3, 4, 5])) - assert.deepStrictEqual(range(3, 6, true), matrix([3, 4, 5, 6])) + assert.deepStrictEqual(range(3, 6, false).valueOf(), [3, 4, 5]) + assert.deepStrictEqual(range(3, 6, true).valueOf(), [3, 4, 5, 6]) }) it('should create a range start:step:end and include end', function () { - assert.deepStrictEqual(range(0, 10, 2, false), matrix([0, 2, 4, 6, 8])) - assert.deepStrictEqual(range(0, 10, 2, true), matrix([0, 2, 4, 6, 8, 10])) + assert.deepStrictEqual(range(0, 10, 2, false).valueOf(), [0, 2, 4, 6, 8]) + assert.deepStrictEqual( + range(0, 10, 2, true).valueOf(), [0, 2, 4, 6, 8, 10]) }) it('should create a range with bignumbers and include end', function () { - assert.deepStrictEqual(range(bignumber(1), bignumber(3), true), matrix([bignumber(1), bignumber(2), bignumber(3)])) - assert.deepStrictEqual(range(bignumber(3), bignumber(1), bignumber(-1), true), matrix([bignumber(3), bignumber(2), bignumber(1)])) + assert.deepStrictEqual( + range(bignumber(1), bignumber(3), true).valueOf(), + [bignumber(1), bignumber(2), bignumber(3)]) + assert.deepStrictEqual( + range(bignumber(3), bignumber(1), bignumber(-1), true).valueOf(), + [bignumber(3), bignumber(2), bignumber(1)]) }) it('should handle Fractions', function () { const frac = math.fraction + const fRange = range(frac(1, 3), frac(10, 3)) + assert.deepStrictEqual(fRange, new Range(frac(1, 3), frac(10, 3))) assert.deepStrictEqual( - range(frac(1, 3), frac(10, 3)), - matrix([frac(1, 3), frac(4, 3), frac(7, 3)])) + fRange.valueOf(), [frac(1, 3), frac(4, 3), frac(7, 3)]) assert.deepStrictEqual( - range(frac(1, 3), frac(7, 3), true), - matrix([frac(1, 3), frac(4, 3), frac(7, 3)])) + range(frac(1, 3), frac(7, 3), true).valueOf(), + [frac(1, 3), frac(4, 3), frac(7, 3)]) assert.deepStrictEqual( - range(frac(1, 3), frac(4, 3), frac(1, 3)), - matrix([frac(1, 3), frac(2, 3), frac(1)])) + range(frac(1, 3), frac(4, 3), frac(1, 3)).valueOf(), + [frac(1, 3), frac(2, 3), frac(1)]) assert.deepStrictEqual( - range(frac(1, 3), frac(4, 3), frac(1, 3), true), - matrix([frac(1, 3), frac(2, 3), frac(1), frac(4, 3)])) + range(frac(1, 3), frac(4, 3), frac(1, 3), true).valueOf(), + [frac(1, 3), frac(2, 3), frac(1), frac(4, 3)]) }) it('should allow mixed number and Fraction', function () { const frac = math.fraction + assert.deepStrictEqual(range(1, frac(10, 3)).valueOf(), [1, 2, 3]) assert.deepStrictEqual( - range(1, frac(10, 3)), - matrix([frac(1), frac(2), frac(3)])) - assert.deepStrictEqual( - range(frac(1, 3), 3, true), - matrix([frac(1, 3), frac(4, 3), frac(7, 3)])) + range(frac(1, 3), 3, true).valueOf(), + [frac(1, 3), frac(4, 3), frac(7, 3)]) assert.deepStrictEqual( - range(frac(1, 3), 2, frac(1, 3)), - matrix([frac(1, 3), frac(2, 3), frac(1), frac(4, 3), frac(5, 3)])) + range(frac(1, 3), 2, frac(1, 3)).valueOf(), + [frac(1, 3), frac(2, 3), frac(1), frac(4, 3), frac(5, 3)]) assert.deepStrictEqual( - range(0, frac(4, 3), frac(1, 3), true), - matrix([frac(0), frac(1, 3), frac(2, 3), frac(1), frac(4, 3)])) + range(0, frac(4, 3), frac(1, 3), true).valueOf(), + [frac(0), frac(1, 3), frac(2, 3), frac(1), frac(4, 3)]) }) it('should throw an error in case of invalid type of include end', function () { @@ -214,8 +269,11 @@ describe('range', function () { assert.throws(function () { range(math.unit('5cm')) }, TypeError) }) - it('should throw an error if called with only two units value', function () { - assert.throws(function () { range(math.unit('0cm'), math.unit('5cm')) }, TypeError) + it('should use a step of 1 unit when called with two units', function () { + const unitRange = range(math.unit(0, 'cm'), math.unit(5, 'cm')) + assert.deepStrictEqual(unitRange.step, math.unit(1, 'cm')) + assert.strictEqual(unitRange.length, 5) + assert.strictEqual(unitRange.toString(), '0 cm:5 cm') }) it('should throw an error when called with mismatching units', function () { @@ -242,7 +300,7 @@ describe('range', function () { assert.throws(function () { range(1, 2, 3, true, 5) }, /TypeError: Too many arguments/) }) - it('should not cast a single number or boolean to string', function () { + it('should not cast a single number or boolean to range', function () { assert.throws(function () { range(2) }, /TypeError: Too few arguments/) assert.throws(function () { range(true) }, /TypeError: Unexpected type of argument/) }) diff --git a/test/unit-tests/function/matrix/subset.test.js b/test/unit-tests/function/matrix/subset.test.js index a99fcfcac4..aab10dc5b8 100644 --- a/test/unit-tests/function/matrix/subset.test.js +++ b/test/unit-tests/function/matrix/subset.test.js @@ -6,6 +6,7 @@ import sinon from 'sinon' const subset = math.subset const matrix = math.matrix const Range = math.Range +const range = math.range const index = math.index describe('subset', function () { @@ -16,10 +17,13 @@ describe('subset', function () { assert.deepStrictEqual(subset(a, index(new Range(0, 2), 1)), [2, 4]) assert.deepStrictEqual(subset(a, index(1, 0)), 3) assert.deepStrictEqual(subset([math.bignumber(2)], index(0)), math.bignumber(2)) + assert.deepStrictEqual(subset(a, 1), [3, 4]) + assert.deepStrictEqual(subset(a, [range(0, 2), 1]), [2, 4]) }) it('should get the right subset of an array of booleans', function () { assert.deepStrictEqual(subset(a, index([true, true], [1])), [[2], [4]]) + assert.deepStrictEqual(subset(a, [[true, true], [1]]), [[2], [4]]) assert.deepStrictEqual(subset(a, index([false, true], [true, false])), [[3]]) assert.deepStrictEqual(subset([math.bignumber(2)], index([true])), [math.bignumber(2)]) }) @@ -29,6 +33,7 @@ describe('subset', function () { assert.deepStrictEqual(subset(a, index(new math.Range(0, 0), 1)), []) assert.deepStrictEqual(subset(b, index([], 1)), math.matrix()) assert.deepStrictEqual(subset(b, index(new math.Range(0, 0), 1)), math.matrix()) + assert.deepStrictEqual(subset(b, [range(0, 0), 1]), math.matrix()) assert.deepStrictEqual(subset({ a: 1 }, index('')), undefined) assert.deepStrictEqual(subset('hello', index('')), '') }) @@ -66,6 +71,7 @@ describe('subset', function () { it('should get the right subset of an object', function () { const obj = { foo: 'bar' } assert.deepStrictEqual(subset(obj, index('foo')), 'bar') + assert.deepStrictEqual(subset(obj, ['foo']), 'bar') assert.deepStrictEqual(subset(obj, index('bla')), undefined) }) @@ -78,6 +84,8 @@ describe('subset', function () { it('should get the right subset of a matrix', function () { assert.deepStrictEqual(subset(b, index(new Range(0, 2), 1)), matrix([2, 4])) + assert.deepStrictEqual(subset(b, [range(0, 2), 1]), matrix([2, 4])) + assert.deepStrictEqual(subset(b, 0), matrix([1, 2])) assert.deepStrictEqual(subset(b, index(1, 0)), 3) }) @@ -244,10 +252,11 @@ describe('subset', function () { assert.throws(function () { subset('hello', index(10), '!', 'foo') }, /Single character expected as defaultValue/) }) - it('should throw an error if in case of an invalid index type', function () { - assert.throws(function () { subset('hello', 2) }, /TypeError: Unexpected type of argument/) - assert.throws(function () { subset('hello', 2, 'A') }, /TypeError: Unexpected type of argument/) - assert.throws(function () { subset('hello', 2, 'A', 'B') }, /TypeError: Unexpected type of argument/) + it('should handle integer indices', function () { + // These now all work: + assert.strictEqual(subset('hello', 2), 'l') + assert.strictEqual(subset('hello', 2, 'A'), 'heAlo') + assert.strictEqual(subset('hello', 2, 'A', 'B'), 'heAlo') }) }) @@ -258,9 +267,10 @@ describe('subset', function () { }) it('should throw an error in case of invalid type of arguments', function () { - assert.throws(function () { subset([1, 2], [0]) }, /TypeError: Unexpected type of argument/) - // assert.throws(function () {subset(new Date(), index(0))}, /TypeError: Unexpected type of argument/) // FIXME: should fail too. Problem is, Date is also an Object - // assert.throws(function () {subset(/foo/, index(0))}, /TypeError: Unexpected type of argument/) // FIXME: should fail too. Problem is, Date is also an Object + // This first one now works: + assert.strictEqual(subset([1, 2], [0]), 1) + // assert.throws(function () {subset(new Date(), index(0))}, /TypeError: Unexpected type of argument/) // FIXME: should fail. Problem is, Date is also an Object + // assert.throws(function () {subset(/foo/, index(0))}, /TypeError: Unexpected type of argument/) // FIXME: should fail too. Problem is, RegExp is also an Object }) it('should LaTeX subset', function () { diff --git a/test/unit-tests/function/matrix/zeros.test.js b/test/unit-tests/function/matrix/zeros.test.js index d386c012b0..46b1950890 100644 --- a/test/unit-tests/function/matrix/zeros.test.js +++ b/test/unit-tests/function/matrix/zeros.test.js @@ -33,6 +33,20 @@ describe('zeros', function () { assert.deepStrictEqual(zeros([three]), [zero, zero, zero]) }) + it('should create a sparse matrix with zeros', function () { + assert.deepStrictEqual( + zeros(2, 3, 'sparse'), matrix([[0, 0, 0], [0, 0, 0]], 'sparse')) + }) + + it('should create a Range of zero vectors', function () { + const zeroRange = zeros(2, 3, 'range') + assert.strictEqual(zeroRange.length, 2) + assert.strictEqual(zeroRange.step, 0) + assert.deepStrictEqual( + zeroRange.start, new math.Range({ start: 0, step: 0, length: 3 })) + assert.strictEqual(zeroRange.get([1, 1]), 0) + }) + it('should create a 2D matrix with zeros from an array', function () { assert.deepStrictEqual(zeros(2, 3), matrix([[0, 0, 0], [0, 0, 0]])) assert.deepStrictEqual(zeros(3, 2), matrix([[0, 0], [0, 0], [0, 0]])) diff --git a/test/unit-tests/function/probability/factorial.test.js b/test/unit-tests/function/probability/factorial.test.js index dde6bf970c..fb1e3c94ed 100644 --- a/test/unit-tests/function/probability/factorial.test.js +++ b/test/unit-tests/function/probability/factorial.test.js @@ -1,5 +1,4 @@ import assert from 'assert' -import { approxEqual } from '../../../../tools/approx.js' import math from '../../../../src/defaultInstance.js' const factorial = math.factorial @@ -34,6 +33,19 @@ describe('factorial', function () { assert.deepStrictEqual(bigmath20.factorial(bigmath20.bignumber(22)), bigmath20.bignumber('1124000727777607680000')) }) + it('should calculate the factorial of a bigint', function () { + assert.strictEqual(factorial(23n), 25852016738884976640000n) + }) + + it('should return factorial of integer Fraction as bigint', function () { + assert.strictEqual(factorial(math.fraction(24)), 620448401733239439360000n) + }) + + it('should handle complex numbers (trivially)', function () { + assert.throws(() => factorial(math.complex(1, 1)), RangeError) + assert.strictEqual(factorial(math.complex(6, 0)), 720) + }) + it('should calculate the factorial of a boolean', function () { assert.strictEqual(factorial(true), 1) assert.strictEqual(factorial(false), 1) @@ -47,18 +59,18 @@ describe('factorial', function () { assert.deepStrictEqual(factorial([0, 1, 2, 3, 4, 5]), [1, 1, 2, 6, 24, 120]) }) - it('should calculate the factorial of a non-integer', function () { - approxEqual(factorial(1.5), 1.32934038817914) - approxEqual(factorial(7.5), 14034.40729348) + it('should throw on non-integers (use gamma)', function () { + assert.throws(() => factorial(1.5), RangeError) }) it('should throw error if called with negative number', function () { - assert.throws(function () { factorial(-1) }, /Value must be non-negative/) - assert.throws(function () { factorial(-1.5) }, /Value must be non-negative/) + assert.throws(function () { factorial(-1) }, RangeError) + assert.throws(function () { factorial(-1.5) }, RangeError) - assert.throws(function () { factorial(math.bignumber(-1)) }, /Value must be non-negative/) - assert.throws(function () { factorial(math.bignumber(-1.5)) }, /Value must be non-negative/) - assert.throws(function () { factorial(math.bignumber(-Infinity)) }, /Value must be non-negative/) + assert.throws(function () { factorial(math.bignumber(-1)) }, RangeError) + assert.throws(function () { factorial(math.bignumber(-1.5)) }, RangeError) + assert.throws( + function () { factorial(math.bignumber(-Infinity)) }, RangeError) }) it('should throw an error if called with non-integer bignumber', function () { diff --git a/test/unit-tests/function/relational/deepEqual.test.js b/test/unit-tests/function/relational/deepEqual.test.js index 98d3c20ce0..7fc7ad375f 100644 --- a/test/unit-tests/function/relational/deepEqual.test.js +++ b/test/unit-tests/function/relational/deepEqual.test.js @@ -49,6 +49,11 @@ describe('deepEqual', function () { assert.deepStrictEqual(deepEqual([complex(2, 3), 3], [complex(2, 4), 3]), false) }) + it('should compare two units', function () { + assert.strictEqual(deepEqual(math.unit(1, 'm'), math.unit(100, 'cm')), true) + assert.strictEqual(deepEqual(math.unit(1, 'm'), math.unit(99, 'cm')), false) + }) + it('should throw an error in case of invalid number of arguments', function () { assert.throws(function () { deepEqual(1) }, /TypeError: Too few arguments/) assert.throws(function () { deepEqual(1, 2, 3) }, /TypeError: Too many arguments/) diff --git a/test/unit-tests/function/statistics/prod.test.js b/test/unit-tests/function/statistics/prod.test.js index 87d43d5aa7..f1cbbd491f 100644 --- a/test/unit-tests/function/statistics/prod.test.js +++ b/test/unit-tests/function/statistics/prod.test.js @@ -59,6 +59,13 @@ describe('prod', function () { assert.strictEqual(prod(new DenseMatrix([1, 3, 5, 2])), 30) }) + it('should return the prod of a Range', function () { + assert.strictEqual(prod(math.range(8, 1, -2)), 8 * 6 * 4 * 2) // 8!! + // and 2.5 rising 4: + assert.strictEqual( + prod(math.range({ start: 2.5, length: 4 })), 2.5 * 3.5 * 4.5 * 5.5) + }) + it('should return the prod element from a 2d array', function () { assert.deepStrictEqual(prod([ [1, 7, 2], @@ -86,12 +93,18 @@ describe('prod', function () { assert.throws(function () { prod() }) }) - it('should throw an error if called with not yet supported argument dim', function () { - assert.throws(function () { prod([], 2) }, /not yet supported/) + it('should throw an error if argument dim is too large', function () { + assert.throws(function () { prod([], 2) }, /no dimension 2/) + }) + + it('should multiply along either dimension of a 2D array', function () { + const nums = [[1, 2], [3, 4], [5, 6]] + assert.deepStrictEqual(prod(nums, 0), [15, 48]) + assert.deepStrictEqual(prod(nums, 1), [2, 12, 30]) }) - it('should throw an error if called with an empty array', function () { - assert.throws(function () { prod([]) }) + it('should return 1 on an empty array', function () { + assert.strictEqual(prod([]), 1) }) it('should throw an error if called with invalid type of arguments', function () { diff --git a/test/unit-tests/function/string/format.test.js b/test/unit-tests/function/string/format.test.js index 8469a571d6..7fc943be6c 100644 --- a/test/unit-tests/function/string/format.test.js +++ b/test/unit-tests/function/string/format.test.js @@ -65,7 +65,8 @@ describe('format', function () { }) it('should format ranges with given precision', function () { - assert.strictEqual(math.format(new math.Range(1 / 3, 4 / 3, 2 / 3), 3), '0.333:0.667:1.33') + assert.strictEqual( + math.format(new math.Range(1 / 3, 4 / 3, 2 / 3), 3), '0.333:0.667:1.67') }) }) diff --git a/test/unit-tests/json/replacer.test.js b/test/unit-tests/json/replacer.test.js index e1695eeca3..202dcac74e 100644 --- a/test/unit-tests/json/replacer.test.js +++ b/test/unit-tests/json/replacer.test.js @@ -50,7 +50,7 @@ describe('replacer', function () { it('should stringify a Range', function () { const r = new math.Range(2, 10) - const json = '{"mathjs":"Range","start":2,"end":10,"step":1}' + const json = '{"mathjs":"Range","start":2,"step":1,"length":8}' assert.deepStrictEqual(JSON.stringify(r), json) assert.deepStrictEqual(JSON.stringify(r, replacer), json) }) @@ -58,16 +58,14 @@ describe('replacer', function () { it('should stringify an Index', function () { const i = new math.Index(new math.Range(0, 10), 2) const json = '{"mathjs":"Index","dimensions":[' + - '{"mathjs":"Range","start":0,"end":10,"step":1},' + - '2' + - ']}' + '{"mathjs":"Range","start":0,"step":1,"length":10},2]}' assert.deepStrictEqual(JSON.stringify(i), json) assert.deepStrictEqual(JSON.stringify(i, replacer), json) }) it('should stringify a Range (2)', function () { const r = new math.Range(2, 10, 2) - const json = '{"mathjs":"Range","start":2,"end":10,"step":2}' + const json = '{"mathjs":"Range","start":2,"step":2,"length":4}' assert.deepStrictEqual(JSON.stringify(r), json) assert.deepStrictEqual(JSON.stringify(r, replacer), json) }) diff --git a/test/unit-tests/type/matrix/DenseMatrix.test.js b/test/unit-tests/type/matrix/DenseMatrix.test.js index 1540eb3146..4d9d61242b 100644 --- a/test/unit-tests/type/matrix/DenseMatrix.test.js +++ b/test/unit-tests/type/matrix/DenseMatrix.test.js @@ -488,6 +488,8 @@ describe('DenseMatrix', function () { m = new DenseMatrix(math.range(0, 10)) assert.deepStrictEqual(m.size(), [10]) assert.deepStrictEqual(m.subset(index(new Range(2, 5))).valueOf(), [2, 3, 4]) + assert.deepStrictEqual( + m.subset(index('6:')), new DenseMatrix([6, 7, 8, 9], 'number')) // get 2-dimensional m = new DenseMatrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) @@ -499,6 +501,7 @@ describe('DenseMatrix', function () { assert.deepStrictEqual(m.subset(index(new Range(1, 3), 1)).valueOf(), [5, 8]) assert.deepStrictEqual(m.subset(index(new Range(1, 3), 2)).valueOf(), [6, 9]) assert.deepStrictEqual(m.subset(index([0, 1, 2], [1])).valueOf(), [[2], [5], [8]]) + assert.deepStrictEqual(m.layer(1), new DenseMatrix([4, 5, 6])) // get n-dimensional m = new DenseMatrix([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) @@ -514,7 +517,8 @@ describe('DenseMatrix', function () { it('should squeeze the output when index contains a scalar', function () { let m = new DenseMatrix(math.range(0, 10)) assert.deepStrictEqual(m.subset(index(1)), 1) - assert.deepStrictEqual(m.subset(index(new Range(1, 2))), new DenseMatrix([1])) + assert.deepStrictEqual( + m.subset(index(new Range(1, 2))), new DenseMatrix([1], 'number')) m = new DenseMatrix([[1, 2], [3, 4]]) assert.deepStrictEqual(m.subset(index(1, 1)), 4) @@ -550,9 +554,11 @@ describe('DenseMatrix', function () { // set 1-dimensional let m = new DenseMatrix(math.range(0, 7)) m.subset(index(new Range(2, 4)), [20, 30]) - assert.deepStrictEqual(m, new DenseMatrix([0, 1, 20, 30, 4, 5, 6])) + assert.deepStrictEqual( + m, new DenseMatrix([0, 1, 20, 30, 4, 5, 6], 'number')) m.subset(index(4), 40) - assert.deepStrictEqual(m, new DenseMatrix([0, 1, 20, 30, 40, 5, 6])) + assert.deepStrictEqual( + m, new DenseMatrix([0, 1, 20, 30, 40, 5, 6], 'number')) // set 2-dimensional m = new DenseMatrix() diff --git a/test/unit-tests/type/matrix/ImmutableDenseMatrix.test.js b/test/unit-tests/type/matrix/ImmutableDenseMatrix.test.js index 57d8d33436..e82d1f2be6 100644 --- a/test/unit-tests/type/matrix/ImmutableDenseMatrix.test.js +++ b/test/unit-tests/type/matrix/ImmutableDenseMatrix.test.js @@ -274,7 +274,9 @@ describe('ImmutableDenseMatrix', function () { it('should squeeze the output when index contains a scalar', function () { let m = new ImmutableDenseMatrix(math.range(0, 10)) assert.deepStrictEqual(m.subset(index(1)), 1) - assert.deepStrictEqual(m.subset(index(new Range(1, 2))), new ImmutableDenseMatrix([1])) + assert.deepStrictEqual( + m.subset(index(new Range(1, 2))), + new ImmutableDenseMatrix([1], 'number')) m = new ImmutableDenseMatrix([[1, 2], [3, 4]]) assert.deepStrictEqual(m.subset(index(1, 1)), 4) diff --git a/test/unit-tests/type/matrix/Index.test.js b/test/unit-tests/type/matrix/Index.test.js index 1e5abccd47..f7269f915e 100644 --- a/test/unit-tests/type/matrix/Index.test.js +++ b/test/unit-tests/type/matrix/Index.test.js @@ -52,18 +52,6 @@ describe('Index', function () { assert.deepStrictEqual(new Index(new ImmutableDenseMatrix([0, 10]))._dimensions, [new ImmutableDenseMatrix([0, 10])]) }) - it('should create an Index from an array with ranges', function () { - const index = Index.create([new Range(0, 10), new Range(4)]) - assert(index instanceof Index) - assert.deepStrictEqual(index._dimensions, [new Range(0, 10), new Range(4)]) - }) - - it('should create an Index from an array with sets', function () { - const index = Index.create([new ImmutableDenseMatrix([0, 10]), new ImmutableDenseMatrix([4])]) - assert(index instanceof Index) - assert.deepStrictEqual(index._dimensions, [new ImmutableDenseMatrix([0, 10]), new ImmutableDenseMatrix([4])]) - }) - it('should calculate the size of an Index', function () { assert.deepStrictEqual(new Index(new Range(0, 10)).size(), [10]) assert.deepStrictEqual(new Index(new Range(0, 10, 2)).size(), [5]) diff --git a/test/unit-tests/type/matrix/Range.test.js b/test/unit-tests/type/matrix/Range.test.js index 69a54f992f..3b22f90d72 100644 --- a/test/unit-tests/type/matrix/Range.test.js +++ b/test/unit-tests/type/matrix/Range.test.js @@ -42,26 +42,124 @@ describe('range', function () { assert.deepStrictEqual(r.size(), [0]) }) + it('should create ranges from attributes', function () { + const frac = math.fraction + assert.deepStrictEqual(new Range({}).toArray(), []) + + assert.deepStrictEqual(new Range({ start: 3 }).toArray(), []) + assert.deepStrictEqual(new Range({ end: 3 }).toArray(), [0, 1, 2]) + assert.deepStrictEqual(new Range({ step: 3 }).toArray(), []) + assert.deepStrictEqual(new Range({ last: 3 }).toArray(), [0, 1, 2, 3]) + assert.deepStrictEqual(new Range({ length: 3 }).toArray(), [0, 1, 2]) + + assert.deepStrictEqual( + new Range({ start: 3, end: 7 }).toArray(), [3, 4, 5, 6]) + assert.deepStrictEqual( + new Range({ start: 3, step: 3 }).toArray(), []) + assert.deepStrictEqual( + new Range({ start: 3, last: 7 }).toArray(), [3, 4, 5, 6, 7]) + assert.deepStrictEqual( + new Range({ start: 3, length: 3 }).toArray(), [3, 4, 5]) + assert.deepStrictEqual( + new Range({ end: 7, step: 3 }).toArray(), [0, 3, 6]) + assert.deepStrictEqual( + new Range({ end: 7, last: 6 }).toArray(), [0, 1, 2, 3, 4, 5, 6]) + assert.deepStrictEqual( // last takes precedence: + new Range({ end: 7, last: 3 }).toArray(), [0, 1, 2, 3]) + assert.deepStrictEqual( + new Range({ end: 7, length: 3 }).toArray(), [4, 5, 6]) + assert.deepStrictEqual( + new Range({ step: 3, last: 7 }).toArray(), [0, 3, 6]) + assert.deepStrictEqual( + new Range({ step: 3, length: 3 }).toArray(), [0, 3, 6]) + assert.deepStrictEqual( + new Range({ length: 3, last: 7 }).toArray(), [5, 6, 7]) + + assert.deepStrictEqual( + new Range({ step: 3, last: 7, length: 3 }).toArray(), [1, 4, 7]) + assert.deepStrictEqual( + new Range({ end: 3, last: 7, length: 3 }).toArray(), [5, 6, 7]) + assert.deepStrictEqual( + new Range({ step: 3, end: 12, length: 3 }).toArray(), [3, 6, 9]) + assert.deepStrictEqual( + new Range({ step: 3, end: 3, last: 10 }).toArray(), [0, 3, 6, 9]) + assert.deepStrictEqual( + new Range({ start: 7, last: 3, length: 3 }).toArray(), [7, 5, 3]) + assert.deepStrictEqual( + new Range({ step: 7, start: 6, length: 3 }).toArray(), [6, 13, 20]) + assert.deepStrictEqual( + new Range({ step: 7, start: 3, last: 12 }).toArray(), [3, 10]) + assert.deepStrictEqual( + new Range({ start: frac(3), end: frac(7), length: 3 }).toArray(), + [frac(3), frac(13, 3), frac(17, 3)]) + assert.deepStrictEqual( + new Range({ start: 3, end: 3, last: 4 }).toArray(), [3, 4]) + assert.deepStrictEqual( + new Range({ start: 3, step: 2, end: 7 }).toArray(), [3, 5]) + + assert.deepStrictEqual( + new Range({ end: -3, step: -1, last: 3, length: 4 }).toArray(), + [6, 5, 4, 3]) + assert.deepStrictEqual( // last overridden when start, step, length given + new Range({ start: 5, step: -1, last: 3, length: 4 }).toArray(), + [5, 4, 3, 2]) + assert.deepStrictEqual( + new Range({ start: 5, end: 3, last: 3.5, length: 4 }).toArray(), + [5, 4.5, 4, 3.5]) + assert.deepStrictEqual( // end overridden similarly + new Range({ start: 5, end: 3, step: 2, length: 3 }).toArray(), + [5, 7, 9]) + assert.deepStrictEqual( + new Range({ start: 5, end: 3, step: 3, last: 10 }).toArray(), + [5, 8]) + + assert.deepStrictEqual( + new Range({ start: 2, end: 20, step: -0.25, last: 10, length: 3 }) + .toArray(), + [2, 1.75, 1.5]) + }) + + it('can make 2D ranges', function () { + const rng2d = new Range({ start: [1, 3], step: [1, -1], length: 3 }) + assert.deepStrictEqual(rng2d.toArray(), [[1, 3], [2, 2], [3, 1]]) + assert.strictEqual(rng2d.toString(), '[1, 3]:[1, -1]:[4, 0]') + assert.deepStrictEqual( + new Range({ start: [1, 3, 5], last: [0, 0, 0], length: 3 }).toArray(), + [[1, 3, 5], [0.5, 1.5, 2.5], [0, 0, 0]]) + const numberGrid = new Range(new Range(1, 11), new Range(101, 111), 10) + assert.strictEqual(numberGrid.get([4, 5]), 46) + assert.deepStrictEqual(numberGrid.last, new Range(91, 101)) + assert.strictEqual(numberGrid.toString(), '(1:11):10:(101:111)') + }) + it('should throw an error when created without new keyword', function () { assert.throws(function () { Range(0, 10) }, /Constructor must be called with the new operator/) }) it('should throw an error for wrong type of arguments', function () { - assert.throws(function () { console.log(new Range('str', 10, 1)) }, /Parameter start must be a number/) - assert.throws(function () { console.log(new Range(0, 'str', 1)) }, /Parameter end must be a number/) - assert.throws(function () { console.log(new Range(0, 10, 'str')) }, /Parameter step must be a number/) + assert.throws(function () { console.log(new Range('str', 10, 1)) }, /Cannot convert/) + assert.throws(function () { console.log(new Range(0, 'str', 1)) }, /Cannot convert/) + assert.throws(function () { console.log(new Range(0, 10, 'str')) }, /Cannot convert/) }) - it('should throw an error for step size zero', function () { - assert.throws(function () { console.log(new Range(0, 0, 0)) }, /Step must not be zero/) - assert.throws(function () { console.log(new Range(10, 10, 0)) }, /Step must not be zero/) - assert.throws(function () { console.log(new Range(0, 10, math.bignumber(0))) }, /Step must not be zero/) - assert.throws(function () { console.log(new Range(0, 10, math.bigint(0))) }, /Step must not be zero/) + it('should deal carefully with step size zero', function () { + let empty = new Range(0, 0, 0) + assert.strictEqual(empty.length, 0) + empty = new Range(10, 10, 0) + assert.strictEqual(empty.length, 0) + assert.throws(function () { + console.log(new Range(0, 10, math.bignumber(0))) + }, /No scalar/) + assert.throws(function () { + console.log(new Range(0, 10, math.bigint(0))) + }, /No scalar/) }) }) describe('parse', function () { - it('should create a range from a string', function () { + it('should create a range from a string [deprecated]', function () { + Range.parseMethodMustWarn = false // suppress deprecation warning + let r = Range.parse('10:-1:4') assert.deepStrictEqual(r.toArray(), [10, 9, 8, 7, 6, 5]) assert.deepStrictEqual(r.size(), [6]) @@ -71,7 +169,7 @@ describe('range', function () { assert.deepStrictEqual(r.size(), [4]) }) - it('should return null when parsing an invalid string', function () { + it('should return null when parsing an invalid string [deprecated]', function () { assert.strictEqual(Range.parse('a:4'), null) assert.strictEqual(Range.parse('3'), null) assert.strictEqual(Range.parse(''), null) @@ -144,15 +242,30 @@ describe('range', function () { }) describe('toString', function () { - it('should stringify a range to format start:step:end', function () { + it('should stringify a range to format from:by:to', function () { assert.strictEqual(new math.Range(0, 10).toString(), '0:10') assert.strictEqual(new math.Range(0, 10, 2).toString(), '0:2:10') }) - it('should stringify a range to format start:step:end with given precision', function () { - assert.strictEqual(new math.Range(1 / 3, 4 / 3, 2 / 3).format(3), '0.333:0.667:1.33') - assert.strictEqual(new math.Range(1 / 3, 4 / 3, 2 / 3).format(4), '0.3333:0.6667:1.333') - assert.strictEqual(new math.Range(1 / 3, 4 / 3, 2 / 3).format(14), '0.33333333333333:0.66666666666667:1.3333333333333') + it( + 'should stringify a range to format start:step:end with given precision', + function () { + assert.strictEqual( + new math.Range(1 / 3, 4 / 3, 2 / 3).format(3), '0.333:0.667:1.67') + assert.strictEqual( + new math.Range(1 / 3, 4 / 3, 2 / 3).format(4), '0.3333:0.6667:1.667') + assert.strictEqual( + new math.Range(1 / 3, 4 / 3, 2 / 3).format(14), + '0.33333333333333:0.66666666666667:1.6666666666667' + ) + }) + }) + + describe('immutable', function () { + it('should not allow property changes', function () { + const r1 = new Range(0, 10, 2) + assert.throws(() => { r1.start = 2 }, TypeError) + assert.throws(() => { r1.length = 3 }, TypeError) }) }) @@ -163,18 +276,6 @@ describe('range', function () { assert.deepStrictEqual(r1, r2) assert.notStrictEqual(r1, r2) - - // changes in r1 should not affect r2 - r1.start = 2 - r1.end = 8 - r1.step = 1 - - assert.strictEqual(r1.start, 2) - assert.strictEqual(r1.end, 8) - assert.strictEqual(r1.step, 1) - assert.strictEqual(r2.start, 0) - assert.strictEqual(r2.end, 10) - assert.strictEqual(r2.step, 2) }) }) @@ -196,12 +297,12 @@ describe('range', function () { assert.deepStrictEqual(r.map(function (value, index, range) { assert.strictEqual(range, r) return 'range[' + index[0] + ']=' + value - }), [ + }), math.matrix([ 'range[0]=2', 'range[1]=3', 'range[2]=4', 'range[3]=5' - ]) + ])) }) }) @@ -228,10 +329,12 @@ describe('range', function () { assert.strictEqual(new Range(0, 4).format(), '0:4') assert.strictEqual(new Range(0, 4, 2).format(), '0:2:4') - assert.strictEqual(new Range(0.01, 0.09, 0.02).format(), '0.01:0.02:0.09') + assert.strictEqual( + new Range(0.01, 0.09, 0.02).format(2), '0.01:0.02:0.09') assert.strictEqual(new Range(0.01, 0.09, 0.02).format({ - notation: 'exponential' + notation: 'exponential', + precision: 1 }), '1e-2:2e-2:9e-2') }) }) @@ -251,8 +354,12 @@ describe('range', function () { }) it('toJSON', function () { - assert.deepStrictEqual(new Range(2, 4).toJSON(), { mathjs: 'Range', start: 2, end: 4, step: 1 }) - assert.deepStrictEqual(new Range(0, 10, 2).toJSON(), { mathjs: 'Range', start: 0, end: 10, step: 2 }) + assert.deepStrictEqual( + new Range(2, 4).toJSON(), + { mathjs: 'Range', start: 2, step: 1, length: 2 }) + assert.deepStrictEqual( + new Range(0, 10, 2).toJSON(), + { mathjs: 'Range', start: 0, step: 2, length: 5 }) }) it('fromJSON', function () { @@ -267,5 +374,10 @@ describe('range', function () { assert.strictEqual(r2.start, 0) assert.strictEqual(r2.end, 10) assert.strictEqual(r2.step, 2) + + // Make sure old format works: + const oldToJSON = '{"mathjs": "Range", "start": 2, "end": 4, "step": 1}' + const revived = JSON.parse(oldToJSON, math.reviver) + assert.deepStrictEqual(revived, new Range(2, 4)) }) }) diff --git a/test/unit-tests/type/matrix/function/matrix.test.js b/test/unit-tests/type/matrix/function/matrix.test.js index 7b335f1f86..93753b3c51 100644 --- a/test/unit-tests/type/matrix/function/matrix.test.js +++ b/test/unit-tests/type/matrix/function/matrix.test.js @@ -72,10 +72,18 @@ describe('matrix', function () { it('should create a matrix from a range correctly', function () { const d = matrix(math.range(1, 6)) assert.ok(d instanceof math.Matrix) - assert.deepStrictEqual(d, matrix([1, 2, 3, 4, 5])) + assert.deepStrictEqual(d.valueOf(), [1, 2, 3, 4, 5]) assert.deepStrictEqual(math.size(d), [5]) }) + it('should create Ranges', function () { + const r1 = math.range(1, 6) + const r2 = matrix(r1, 'range') + assert.ok(r1 !== r2) + assert.deepStrictEqual(r1, r2) + assert.deepStrictEqual(matrix([1, 2, 3, 4, 5], 'range'), r1) + }) + it('should throw an error if called with an invalid argument', function () { assert.throws(function () { matrix(new Date()) }, TypeError) }) diff --git a/test/unit-tests/type/unit/Unit.test.js b/test/unit-tests/type/unit/Unit.test.js index 2803daf984..9254e6a6a8 100644 --- a/test/unit-tests/type/unit/Unit.test.js +++ b/test/unit-tests/type/unit/Unit.test.js @@ -80,6 +80,7 @@ describe('Unit', function () { it('should create a unitless Unit if second parameter is undefined', function () { const a = new Unit(6) assert(a.dimensions.every(d => d === 0)) + assert.strictEqual(a.unitless(), true) }) it('should ignore properties on Object.prototype', function () { diff --git a/types/index.d.ts b/types/index.d.ts index 05b2711423..f88004f0ca 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -14,16 +14,22 @@ export type NoLiteralType = T extends number : T export type MathNumericType = number | BigNumber | bigint | Fraction | Complex -export type MathScalarType = MathNumericType | Unit -export type MathGeneric = T -export type MathArray = T[] | Array> -export type MathCollection = MathArray | Matrix -export type MathType = MathScalarType | MathCollection +type MathScalarTypeOut = MathNumericType | Unit +type MathScalarTypeIn = MathScalarTypeOut | string | boolean +export type MathScalarType = MathScalarTypeOut + +export type MathArray = + | T[] + | Array> +export type MathCollection = + | MathArray + | Matrix +export type MathType = MathScalarTypeOut | MathCollection export type MathExpression = string | string[] | MathCollection // add type for Matrix Callback Function and Matrix Storage Format -export type MatrixStorageFormat = 'dense' | 'sparse' -export type MatrixFromFunctionCallback = ( +export type MatrixStorageFormat = 'dense' | 'sparse' | 'range' +export type MatrixFromFunctionCallback = ( index: number[] ) => T @@ -677,7 +683,8 @@ export interface MathJsInstance extends MathJsFactory { bignumber( x?: number | string | Fraction | BigNumber | bigint | Unit | boolean | null ): BigNumber - bignumber(x: T): T + bignumber(A: MathArray): MathArray + bignumber(M: Matrix): Matrix /** * Create a bigint, which can store integers with arbitrary precision. @@ -815,12 +822,19 @@ export interface MathJsInstance extends MathJsFactory { * @param dataType The Matrix data type * @returns The created Matrix */ + // We give the two most important types, because the type inference + // in the generic seems not to narrow well matrix( - data: MathCollection | string[], + data: MathCollection, format?: MatrixStorageFormat, dataType?: string - ): Matrix - matrix( + ): Matrix + matrix( + data: MathCollection, + format?: MatrixStorageFormat, + dataType?: string + ): Matrix + matrix( data: MathCollection, format?: MatrixStorageFormat, dataType?: string @@ -922,7 +936,7 @@ export interface MathJsInstance extends MathJsFactory { * @returns The created unit */ unit(value: MathNumericType, unit?: string): Unit - unit(value: MathCollection): Unit[] + unit(value: MathCollection): Unit[] /************************************************************************* * Expression functions @@ -1129,15 +1143,9 @@ export interface MathJsInstance extends MathJsFactory { * @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 | string, scope?: MathScope): MathNode - resolve( - node: (MathNode | string)[], - // eslint-disable-next-line @typescript-eslint/no-explicit-any - scope?: MathScope - ): MathNode[] - // eslint-disable-next-line @typescript-eslint/no-explicit-any - resolve(node: Matrix, scope?: MathScope): Matrix + resolve(node: (MathNode | string)[], scope?: MathScope): MathNode[] + resolve(node: Matrix, scope?: MathScope): Matrix /** * Calculate the Sparse Matrix LU decomposition with full pivoting. @@ -1193,7 +1201,11 @@ export interface MathJsInstance extends MathJsFactory { add(x: T, y: T): T add(x: T, y: T, ...values: T[]): T add(x: MathType, y: MathType): MathType - add(x: MathType, y: MathType, ...values: MathType[]): MathType + add( + x: MathType | string, + y: MathType | string, + ...values: (MathType | string)[] + ): MathType /** * Calculate the cubic root of a value. @@ -1329,6 +1341,31 @@ export interface MathJsInstance extends MathJsFactory { dotDivide(x: MathType, y: Unit): Unit dotDivide(x: MathNumericType, y: MathNumericType): MathNumericType + /** + * Determine if one entity is a scalar multiple of another + * @param x Numerator + * @param y Denominator + * @returns + * If there is a scalar (including Complex) r such that + * x = r*y, returns r, otherwise undefined + */ + scalarDivide( + x: MathScalarTypeIn, + y: MathScalarTypeIn + ): MathScalarTypeOut | undefined + scalarDivide( + x: MathCollection, + y: MathCollection + ): MathScalarTypeOut | undefined + scalarDivide( + x: MathScalarTypeIn, + y: MathCollection + ): undefined + scalarDivide( + x: MathCollection, + y: MathScalarTypeOut + ): undefined + /** * Multiply two matrices element wise. The function accepts both * matrices and scalar values. @@ -1392,19 +1429,19 @@ export interface MathJsInstance extends MathJsFactory { * Create a dense matrix from vectors as individual rows. If you pass column vectors, they will be transposed (but not conjugated!) * @param rows - a multi-dimensional number array or matrix */ - matrixFromRows(...rows: Matrix[]): Matrix - matrixFromRows( - ...rows: (T[] | [T][] | Matrix)[] - ): T[][] + matrixFromRows(...rows: T[][]): T[][] + matrixFromRows( + ...rows: (T[] | T[][] | Matrix)[] + ): Matrix /** * Create a dense matrix from vectors as individual columns. If you pass row vectors, they will be transposed (but not conjugated!) * @param cols - a multi-dimensional number array or matrix */ - matrixFromColumns(...cols: Matrix[]): Matrix - matrixFromColumns( - ...cols: (T[] | [T][] | Matrix)[] - ): T[][] + matrixFromColumns(...cols: T[][]): T[][] + matrixFromColumns( + ...cols: (T[] | T[][] | Matrix)[] + ): Matrix /** * Create a matrix by evaluating a generating function at each index. The simplest overload returns a multi-dimensional array as long as size is an array. Passing size as a Matrix or specifying a format will result in returning a Matrix. * @param size - the size of the matrix to be created @@ -1412,32 +1449,32 @@ export interface MathJsInstance extends MathJsFactory { * @param format - The Matrix storage format, either 'dense' or 'sparse' * @param datatype - Type of the values */ - matrixFromFunction( + matrixFromFunction( size: [number], fn: MatrixFromFunctionCallback ): T[] - matrixFromFunction( + matrixFromFunction( size: [number, number], fn: MatrixFromFunctionCallback ): T[][] - matrixFromFunction( + matrixFromFunction( size: number[], fn: MatrixFromFunctionCallback ): MathArray matrixFromFunction( size: Matrix, - fn: MatrixFromFunctionCallback + fn: MatrixFromFunctionCallback ): Matrix matrixFromFunction( size: number[] | Matrix, - fn: MatrixFromFunctionCallback, + fn: MatrixFromFunctionCallback, format: MatrixStorageFormat, datatype?: string ): Matrix matrixFromFunction( size: number[] | Matrix, format: MatrixStorageFormat, - fn: MatrixFromFunctionCallback, + fn: MatrixFromFunctionCallback, datatype?: string ): Matrix /** @@ -1514,14 +1551,16 @@ export interface MathJsInstance extends MathJsFactory { multiply(x: T, y: MathType): Matrix multiply(x: MathType, y: T): Matrix - multiply(x: T, y: T[]): T - multiply(x: T[], y: T): T - multiply(x: T[], y: T[]): T[] - multiply(x: T, y: T): MathScalarType - multiply(x: Unit, y: Unit): Unit - multiply(x: number, y: number): number - multiply(x: MathType, y: MathType, ...values: MathType[]): MathType + multiply(x: T[], y: T[][]): T[] + multiply(x: T[][], y: T[]): T[] + multiply(x: T[][], y: T[][]): T[][] + multiply(x: T, y: T): T multiply(x: T, y: T, ...values: T[]): T + multiply( + x: MathType | string, + y: MathType | string, + ...values: (MathType | string)[] + ): MathType /** * Calculate the norm of a number, vector or matrix. The second @@ -1559,6 +1598,21 @@ export interface MathJsInstance extends MathJsFactory { */ nthRoots(a: number | BigNumber | Complex, n?: number): Array + /** + * Returns the multiplicative identity of the same type as x + * + * @param x Any math entity + * @return Multiplicative identity of the type of x + */ + one(x: number): 1 + one(x: BigNumber): BigNumber + one(x: Complex): Complex + one(x: bigint): 1n + one(x: Fraction): Fraction + one(x: boolean): true + one(x: Unit): Unit + one(x: MathCollection): MathCollection + /** * Calculates the power of x to y, x ^ y. Matrix exponentiation is * supported for square matrices x, and positive integer exponents y. @@ -1634,6 +1688,21 @@ export interface MathJsInstance extends MathJsFactory { */ xgcd(a: number | BigNumber, b: number | BigNumber): MathArray + /** + * Returns the multiplicative identity of the same type as x + * + * @param x Any math entity + * @return Multiplicative identity of the type of x + */ + zero(x: number): 0 + zero(x: BigNumber): BigNumber + zero(x: Complex): Complex + zero(x: bigint): 0n + zero(x: Fraction): Fraction + zero(x: boolean): true + zero(x: Unit): Unit + zero(x: MathCollection): MathCollection + /************************************************************************* * Bitwise functions ************************************************************************/ @@ -2229,21 +2298,25 @@ export interface MathJsInstance extends MathJsFactory { * @param format The matrix storage format * @returns A matrix filled with ones */ - ones( - size?: number | number[] | BigNumber | BigNumber[], - format?: string - ): MathCollection + ones(): MathCollection + ones(size: number, format?: string): MathCollection + ones(size: BigNumber, format?: string): MathCollection + ones(size: number[], format?: string): MathArray + ones(size: (number | BigNumber)[], format?: string): MathArray + ones(size: Matrix, format?: string): Matrix + ones(size: Matrix, format?: string): Matrix /** * @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, n: number, format?: string): MathCollection ones( m: number | BigNumber, n: number | BigNumber, format?: string - ): MathCollection + ): MathCollection /** * @param m The x dimension of the matrix * @param n The y dimension of the matrix @@ -2251,12 +2324,13 @@ export interface MathJsInstance extends MathJsFactory { * @param format The matrix storage format * @returns A matrix filled with ones */ + ones(m: number, n: number, p: number, format?: string): MathCollection ones( m: number | BigNumber, n: number | BigNumber, p: number | BigNumber, format?: string - ): MathCollection + ): MathCollection /** Actually ones can take an arbitrary number of dimensions before the ** optional format, not sure how to write that in TypeScript **/ @@ -2345,7 +2419,7 @@ export interface MathJsInstance extends MathJsFactory { rotationMatrix( theta?: number | BigNumber | Complex | Unit, axis?: T, - format?: 'sparse' | 'dense' + format?: MatrixStorageFormat ): T /** @@ -2960,27 +3034,37 @@ export interface MathJsInstance extends MathJsFactory { * @param args Multiple scalar values * @returns The maximum value */ - max(...args: T[]): T + // special case for common usage with two values + max( + x: T, + y: U + ): T | U + max(x: string, y: MathScalarTypeIn): MathScalarTypeOut + max(x: MathScalarTypeIn, y: string): MathScalarTypeOut + max(...args: T[]): T /** * @param args Multiple scalar values * @returns The maximum value */ - max(...args: MathScalarType[]): MathScalarType + max(...args: MathScalarTypeIn[]): MathScalarTypeOut /** * @param A A single matrix - * @param dimension The maximum over the selected dimension * @returns The maximum value */ - max( - A: T[] | T[][], - dimension?: number | BigNumber - ): T + max(A: MathCollection): T /** * @param A A single matrix * @param dimension The maximum over the selected dimension * @returns The maximum value */ - max(A: MathCollection, dimension?: number | BigNumber): MathScalarType + max( + A: MathCollection, + dimension?: number | BigNumber + ): T | MathCollection + max( + A: MathCollection, + dimension?: number | BigNumber + ): MathType /** * Compute the mean value of matrix or a list with values. In case of a @@ -2990,27 +3074,26 @@ export interface MathJsInstance extends MathJsFactory { * @param args Multiple scalar values * @returns The mean of all values */ - mean(...args: T[]): T + mean(...args: T[]): T /** * @param args Multiple scalar values * @returns The mean value */ - mean(...args: MathScalarType[]): MathScalarType + mean(...args: MathScalarTypeIn[]): MathScalarTypeOut /** * @param A A single matrix - * @param dimension The mean over the selected dimension * @returns The mean value */ - mean( - A: T[] | T[][], - dimension?: number | BigNumber - ): T + mean(A: MathCollection): T /** * @param A A single matrix * @param dimension The mean over the selected dimension * @returns The mean value */ - mean(A: MathCollection, dimension?: number | BigNumber): MathScalarType + mean( + A: MathCollection, + dimension?: number | BigNumber + ): MathType /** * Compute the median of a matrix or a list with values. The values are @@ -3022,22 +3105,22 @@ export interface MathJsInstance extends MathJsFactory { * @param args Multiple scalar values * @returns The median value */ - median(...args: T[]): T + median(...args: T[]): T /** * @param args Multiple scalar values * @returns The median value */ - median(...args: MathScalarType[]): MathScalarType + median(...args: MathScalarTypeIn[]): MathScalarTypeOut /** * @param A A single matrix * @returns The median value */ - median(A: T[] | T[][]): T + median(A: MathCollection): T /** * @param A A single matrix * @returns The median value */ - median(A: MathCollection): MathScalarType + median(A: MathCollection): MathScalarTypeOut /** * Compute the minimum value of a matrix or a list of values. In case of @@ -3047,27 +3130,37 @@ export interface MathJsInstance extends MathJsFactory { * @param args multiple scalar values * @returns The minimum value */ - min(...args: T[]): T + // special case for common usage of two types + min( + x: T, + y: U + ): T | U + min(x: string, y: MathScalarTypeIn): MathScalarTypeOut + min(x: MathScalarTypeIn, y: string): MathScalarTypeOut + min(...args: T[]): T /** * @param args Multiple scalar values * @returns The minimum value */ - min(...args: MathScalarType[]): MathScalarType + min(...args: MathScalarTypeIn[]): MathScalarTypeOut /** * @param A A single matrix - * @param dimension The minimum over the selected dimension * @returns The minimum value */ - min( - A: T[] | T[][], - dimension?: number | BigNumber - ): T + min(A: MathCollection): T /** * @param A A single matrix * @param dimension The minimum over the selected dimension * @returns The minimum value */ - min(A: MathCollection, dimension?: number | BigNumber): MathScalarType + min( + A: MathCollection, + dimension?: number | BigNumber + ): T | MathCollection + min( + A: MathCollection, + dimension?: number | BigNumber + ): MathType /** * Computes the mode of a set of numbers or a list with values(numbers @@ -3076,22 +3169,17 @@ export interface MathJsInstance extends MathJsFactory { * @param args Multiple scalar values * @returns The mode of all values */ - mode(...args: T[]): T[] + mode(...args: T[]): T[] /** * @param args Multiple scalar values * @returns The mode of all values */ - mode(...args: MathScalarType[]): MathScalarType[] + mode(...args: MathScalarTypeIn[]): MathScalarTypeIn[] /** * @param A A single matrix * @returns The mode value */ - mode(A: T[] | T[][]): T[] - /** - * @param A A single matrix - * @returns The mode of all values - */ - mode(A: MathCollection): MathScalarType[] + mode(A: MathCollection): T[] /** * Compute the product of a matrix or a list with values. In case of a @@ -3100,43 +3188,30 @@ export interface MathJsInstance extends MathJsFactory { * @param args Multiple scalar values * @returns The product of all values */ - prod(...args: T[]): T + prod(...args: T[]): T /** * @param args Multiple scalar values * @returns The product of all values */ - prod(...args: MathScalarType[]): MathScalarType + prod(...args: MathScalarTypeIn[]): MathScalarTypeOut /** * @param A A single matrix * @returns The product of all values */ - prod(A: T[] | T[][]): T + prod(A: MathCollection): T /** * @param A A single matrix * @returns The product of all values */ - prod(A: MathCollection): MathScalarType + prod(A: MathCollection): MathScalarTypeOut - /** - * @param A A single matrix - * @param probOrN prob is the order of the quantile, while N is the - * amount of evenly distributed steps of probabilities; only one of - * these options can be provided - * @param sorted =false is data sorted in ascending order - * @returns Quantile(s) - */ - quantileSeq( - A: T[] | T[][], - prob: number | BigNumber, - sorted?: boolean - ): T /** * Compute the prob order quantile of a matrix or a list with values. - * The sequence is sorted and the middle value is returned. Supported + * The sequence is sorted and the qunatile value(s) are returned. Supported * types of sequence values are: Number, BigNumber, Unit Supported types * of probability are: Number, BigNumber In case of a (multi * dimensional) array or matrix, the prob order quantile of all elements - * will be calculated. + * will be calculated, unless the "dimension" argument is specified. * @param A A single matrix or array * @param probOrN prob is the order of the quantile, while N is the * amount of evenly distributed steps of probabilities; only one of @@ -3144,11 +3219,28 @@ export interface MathJsInstance extends MathJsFactory { * @param sorted =false is data sorted in ascending order * @returns Quantile(s) */ - quantileSeq( - A: MathCollection, - prob: number | BigNumber | MathArray, - sorted?: boolean - ): MathScalarType | MathArray + quantileSeq( + A: MathCollection, + probOrN: number | BigNumber, + dimension: number + ): T | MathCollection // could be a cutoff or a number of quantiles + quantileSeq( + A: MathCollection, + probOrN: number | BigNumber, + sorted?: boolean, + dimension?: number + ): T | MathCollection // could be a cutoff or a number of quantiles + quantileSeq( + A: MathCollection, + prob: MathCollection, + dimension: number + ): MathCollection + quantileSeq( + A: MathCollection, + prob: MathCollection, + sorted?: boolean, + dimension?: number + ): MathCollection /** * Compute the standard deviation of a matrix or a list with values. The @@ -3163,12 +3255,12 @@ export interface MathJsInstance extends MathJsFactory { * @param args variadic argument of number to calculate standard deviation * @returns The standard deviation */ - std(...args: T[]): T + std(...args: T[]): T /** * @param args Multiple scalar values * @returns The standard deviation */ - std(...args: MathScalarType[]): MathScalarType + std(...args: MathScalarTypeIn[]): MathScalarTypeOut /** * Compute the standard deviation of a matrix or a list with values. The * standard deviations is defined as the square root of the variance: @@ -3187,10 +3279,10 @@ export interface MathJsInstance extends MathJsFactory { * @returns The standard deviation array */ std( - array: MathCollection, + array: MathCollection, dimension?: number, normalization?: 'unbiased' | 'uncorrected' | 'biased' - ): MathNumericType[] + ): MathType /** * Compute the standard deviation of a matrix or a list with values. The * standard deviations is defined as the square root of the variance: @@ -3208,9 +3300,9 @@ export interface MathJsInstance extends MathJsFactory { * @returns The standard deviation */ std( - array: MathCollection, + array: MathCollection, normalization: 'unbiased' | 'uncorrected' | 'biased' - ): MathNumericType + ): MathScalarTypeOut /** * Compute the sum of a matrix or a list with values. In case of a @@ -3219,27 +3311,26 @@ export interface MathJsInstance extends MathJsFactory { * @param args A single matrix or multiple scalar values * @returns The sum of all values */ - sum(...args: T[]): T + sum(...args: T[]): T /** * @param args Multiple scalar values * @returns The sum of all values */ - sum(...args: MathScalarType[]): MathScalarType + sum(...args: MathScalarTypeIn[]): MathScalarTypeOut /** * @param A A single matrix - * @param dimension The sum over the selected dimension * @returns The sum of all values */ - sum( - A: T[] | T[][], - dimension?: number | BigNumber - ): T + sum(A: MathCollection): T /** * @param A A single matrix * @param dimension The sum over the selected dimension * @returns The sum of all values */ - sum(A: MathCollection, dimension?: number | BigNumber): MathScalarType + sum( + A: MathCollection, + dimension?: number | BigNumber + ): MathType /** * Count the number of elements of a matrix, array or string. @@ -3734,8 +3825,8 @@ export interface MathJsInstance extends MathJsFactory { * @param x Value to be tested * @returns Boolean | MathCollection */ - isFinite(x: MathScalarType): boolean - isFinite(A: MathCollection): MathCollection + isFinite(x: MathScalarTypeIn): boolean + isFinite(A: MathCollection): MathCollection /** * Test whether a value is an integer number. The function supports @@ -4033,6 +4124,7 @@ export const { divideDependencies, divideScalarDependencies, dotDivideDependencies, + scalarDivideDependencies, dotMultiplyDependencies, dotPowDependencies, expDependencies, @@ -4055,6 +4147,7 @@ export const { normDependencies, nthRootDependencies, nthRootsDependencies, + oneDependencies, powDependencies, roundDependencies, signDependencies, @@ -4064,6 +4157,7 @@ export const { unaryMinusDependencies, unaryPlusDependencies, xgcdDependencies, + zeroDependencies, // bitwise dependencies bitAndDependencies, @@ -4316,16 +4410,16 @@ export const { printTransformDependencies }: Record -export interface Matrix { +export interface Matrix { type: string storage(): string datatype(): string - create(data: MathArray, datatype?: string): void + create(data: MathArray, datatype?: string): Matrix density(): number // eslint-disable-next-line @typescript-eslint/no-explicit-any subset(index: Index, replacement?: any, defaultValue?: any): Matrix - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get(index: number[]): any + get(index: number[]): T + layer(index: number): T | Matrix // eslint-disable-next-line @typescript-eslint/no-explicit-any set(index: number[], value: any, defaultValue?: number | string): Matrix resize(size: MathCollection, defaultValue?: number | string): Matrix @@ -4333,12 +4427,11 @@ export interface Matrix { size(): number[] map( // eslint-disable-next-line @typescript-eslint/no-explicit-any - callback: (a: any, b: number[], c: Matrix) => any, + callback: (a: T, b: number[], c: Matrix) => any, skipZeros?: boolean ): Matrix forEach( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - callback: (a: any, b: number[], c: Matrix) => void, + callback: (a: T, b: number[], c: Matrix) => void, skipZeros?: boolean ): void toArray(): MathArray @@ -4350,9 +4443,6 @@ export interface Matrix { toString(): string // eslint-disable-next-line @typescript-eslint/no-explicit-any toJSON(): any - // eslint-disable-next-line @typescript-eslint/no-explicit-any - diagonal(k?: number | BigNumber): any[] - swapRows(i: number, j: number): Matrix } export interface MatrixCtor { @@ -4997,7 +5087,7 @@ export interface MathJsChain { */ matrix( this: MathJsChain, - format?: 'sparse' | 'dense', + format?: MatrixStorageFormat, dataType?: string ): MathJsChain @@ -5500,6 +5590,27 @@ export interface MathJsChain { y: MathNumericType ): MathJsChain + /** + * Check if one entity is a scalar multiple of another. + * @param y Denominator + */ + scalarDivide( + this: MathJsChain, + y: MathScalarTypeIn + ): MathJsChain + scalarDivide( + this: MathJsChain, + y: MathCollection + ): MathJsChain + scalarDivide( + this: MathJsChain, + y: MathCollection + ): MathJsChain + scalarDivide( + this: MathJsChain, + y: MathScalarTypeIn + ): MathJsChain + /** * Multiply two matrices element wise. The function accepts both * matrices and scalar values. @@ -5695,6 +5806,16 @@ export interface MathJsChain { y: number | BigNumber | bigint | Complex ): MathJsChain + /** + * Generate the multiplicative identity of the current type + */ + one(this: MathJsChain): MathJsChain<1> + one(this: MathJsChain): MathJsChain + one(this: MathJsChain): MathJsChain<1n> + one(this: MathJsChain): MathJsChain + one(this: MathJsChain): MathJsChain + one(this: MathJsChain): MathJsChain + /** * Compute the sign of a value. The sign of a value x is: 1 when x > 1 * -1 when x < 0 0 when x == 0 For matrices, the function is evaluated @@ -5754,6 +5875,16 @@ export interface MathJsChain { b: number | BigNumber ): MathJsChain + /** + * Generate the additive identity of the current type + */ + zero(this: MathJsChain): MathJsChain<0> + zero(this: MathJsChain): MathJsChain + zero(this: MathJsChain): MathJsChain<0n> + zero(this: MathJsChain): MathJsChain + zero(this: MathJsChain): MathJsChain + zero(this: MathJsChain): MathJsChain + /** * Count the number of elements of a matrix, array or string. */ @@ -6874,7 +7005,12 @@ export interface MathJsChain { * calculated. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any - prod(this: MathJsChain): MathJsChain + prod( + this: MathJsChain> + ): MathJsChain + prod( + this: MathJsChain> + ): MathJsChain /** * Compute the prob order quantile of a matrix or a list with values. @@ -7347,8 +7483,10 @@ export interface MathJsChain { /** * Test whether a value is finite, works elementwise on collections */ - isFinite(this: MathJsChain): MathJsChain - isFinite(this: MathJsChain): MathJsChain + isFinite(this: MathJsChain): MathJsChain + isFinite( + this: MathJsChain> + ): MathJsChain> /** * Test whether a value is negative: smaller than zero. The function @@ -7574,6 +7712,7 @@ export const { cube, divide, dotDivide, + scalarDivide, dotMultiply, dotPow, exp, @@ -7595,6 +7734,7 @@ export const { norm, nthRoot, nthRoots, + one, pow, round, sign, @@ -7604,6 +7744,7 @@ export const { unaryMinus, unaryPlus, xgcd, + zero, // bitwise bitAnd,