diff --git a/src/expression/embeddedDocs/embeddedDocs.js b/src/expression/embeddedDocs/embeddedDocs.js index 2e4383beb4..82f9c23b4e 100644 --- a/src/expression/embeddedDocs/embeddedDocs.js +++ b/src/expression/embeddedDocs/embeddedDocs.js @@ -143,6 +143,9 @@ import { partitionSelectDocs } from './function/matrix/partitionSelect.js' import { rangeDocs } from './function/matrix/range.js' import { reshapeDocs } from './function/matrix/reshape.js' import { resizeDocs } from './function/matrix/resize.js' +import { broadcastMatricesDocs } from './function/matrix/broadcastMatrices.js' +import { broadcastToDocs } from './function/matrix/broadcastTo.js' +import { broadcastSizesDocs } from './function/matrix/broadcastSizes.js' import { rotateDocs } from './function/matrix/rotate.js' import { rotationMatrixDocs } from './function/matrix/rotationMatrix.js' import { rowDocs } from './function/matrix/row.js' @@ -481,6 +484,9 @@ export const embeddedDocs = { partitionSelect: partitionSelectDocs, range: rangeDocs, resize: resizeDocs, + broadcastMatrices: broadcastMatricesDocs, + broadcastTo: broadcastToDocs, + broadcastSizes: broadcastSizesDocs, reshape: reshapeDocs, rotate: rotateDocs, rotationMatrix: rotationMatrixDocs, diff --git a/src/expression/embeddedDocs/function/matrix/broadcastMatrices.js b/src/expression/embeddedDocs/function/matrix/broadcastMatrices.js new file mode 100644 index 0000000000..5074c05910 --- /dev/null +++ b/src/expression/embeddedDocs/function/matrix/broadcastMatrices.js @@ -0,0 +1,16 @@ +export const broadcastMatricesDocs = { + name: 'broadcastMatrices', + category: 'Matrix', + syntax: [ + 'broadcastMatrices(A, B)' + ], + description: 'Broadcast any number of arrays or matrices against each other.', + examples: [ + 'broadcastMatrices([1, 2, 3], [[1], [2], [3]])', + 'broadcastMatrices([1, 2; 3, 4], [5, 6; 7, 8])', + 'broadcastMatrices([1, 2, 3], [5], [[10], [20], [30]])' + ], + seealso: [ + 'size', 'reshape', 'broadcastSizes', 'broadcastTo' + ] +} diff --git a/src/expression/embeddedDocs/function/matrix/broadcastSizes.js b/src/expression/embeddedDocs/function/matrix/broadcastSizes.js new file mode 100644 index 0000000000..df0be7b4ca --- /dev/null +++ b/src/expression/embeddedDocs/function/matrix/broadcastSizes.js @@ -0,0 +1,16 @@ +export const broadcastSizesDocs = { + name: 'broadcastSizes', + category: 'Matrix', + syntax: [ + 'broadcastSizes(sizeA, sizeB)' + ], + description: 'Broadcast the sizes of matrices to a compatible size', + examples: [ + 'broadcastSizes([3, 1, 3], [3, 3])', + 'broadcastSizes([2, 1], [2, 2])', + 'broadcastSizes([1, 3], [2, 3], [4, 2, 3])' + ], + seealso: [ + 'size', 'reshape', 'broadcastTo', 'broadcastMatrices' + ] +} diff --git a/src/expression/embeddedDocs/function/matrix/broadcastTo.js b/src/expression/embeddedDocs/function/matrix/broadcastTo.js new file mode 100644 index 0000000000..f0376515cc --- /dev/null +++ b/src/expression/embeddedDocs/function/matrix/broadcastTo.js @@ -0,0 +1,15 @@ +export const broadcastToDocs = { + name: 'broadcastTo', + category: 'Matrix', + syntax: [ + 'broadcastTo(A, size)' + ], + description: 'Broadcast a matrix to a compatible size', + examples: [ + 'broadcastTo([1, 2, 3], [3, 3])', + 'broadcastTo([1, 2; 3, 4], [2, 2])' + ], + seealso: [ + 'size', 'reshape', 'broadcastSizes', 'broadcastMatrices' + ] +} diff --git a/src/factoriesAny.js b/src/factoriesAny.js index 15ea5de0d0..3b45209d79 100644 --- a/src/factoriesAny.js +++ b/src/factoriesAny.js @@ -88,6 +88,9 @@ export { createOnes } from './function/matrix/ones.js' export { createRange } from './function/matrix/range.js' export { createReshape } from './function/matrix/reshape.js' export { createResize } from './function/matrix/resize.js' +export { createBroadcastMatrices } from './function/matrix/broadcastMatrices.js' +export { createBroadcastSizes } from './function/matrix/broadcastSizes.js' +export { createBroadcastTo } from './function/matrix/broadcastTo.js' export { createRotate } from './function/matrix/rotate.js' export { createRotationMatrix } from './function/matrix/rotationMatrix.js' export { createRow } from './function/matrix/row.js' diff --git a/src/function/matrix/broadcastMatrices.js b/src/function/matrix/broadcastMatrices.js new file mode 100644 index 0000000000..4cc100c4b5 --- /dev/null +++ b/src/function/matrix/broadcastMatrices.js @@ -0,0 +1,45 @@ +import { broadcastArrays } from '../../utils/array.js' +import { factory } from '../../utils/factory.js' +import { isMatrix } from '../../utils/is.js' + +const name = 'broadcastMatrices' +const dependencies = ['typed'] + +export const createBroadcastMatrices = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { + /** + * Broadcast any number of arrays or matrices against each other. + * + * Syntax: + * + * math.broadcastMatrices(x, y) + * math.broadcastMatrices(x, y, ...) + * + * Examples: + * + * math.broadcastMatrices([1, 2], [[3], [4]]) // returns [[[1, 2], [1, 2]], [[3, 3], [4, 4]]] + * math.broadcastMatrices([2, 3]) // returns [[2, 3]] + * math.broadcastMatrices([2, 3], [3, 1]) // returns [[2, 3], [3, 1]] + * + * See also: + * + * size, reshape, broadcastSizes, broadcastTo + * + * History: + * + * v15.1.1 created + * @param {...(Array|Matrix)} x One or more matrices or arrays + * @return {Array[Array|Matrix]} An array of matrices with the broadcasted sizes. + */ + return typed(name, { + '...Array|Matrix': collections => { + const areMatrices = collections.map(isMatrix) + if (areMatrices.includes(true)) { + const arrays = collections.map((c, i) => areMatrices[i] ? c.valueOf() : c) + const broadcastedArrays = broadcastArrays(...arrays) + const broadcastedCollections = broadcastedArrays.map((arr, i) => areMatrices[i] ? collections[i].create(arr) : arr) + return broadcastedCollections + } + return broadcastArrays(...collections) + } + }) +}) diff --git a/src/function/matrix/broadcastSizes.js b/src/function/matrix/broadcastSizes.js new file mode 100644 index 0000000000..3801fa0b87 --- /dev/null +++ b/src/function/matrix/broadcastSizes.js @@ -0,0 +1,36 @@ +import { broadcastSizes } from '../../utils/array.js' +import { factory } from '../../utils/factory.js' + +const name = 'broadcastSizes' +const dependencies = ['typed'] + +export const createBroadcastSizes = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { + /** + * Calculate the broadcasted size of one or more matrices or arrays. + * Always returns an Array containing numbers. + * + * Syntax: + * + * math.broadcastSizes(x, y) + * math.broadcastSizes(x, y, ...) + * + * Examples: + * + * math.broadcastSizes([2, 3]) // returns [2, 3] + * math.broadcastSizes([2, 3], [3]) // returns [2, 3] + * math.broadcastSizes([1, 2, 3], [1, 2, 1]) // returns [1, 2, 3] + * + * See also: + * + * size, reshape, squeeze, broadcastTo + * + * History: + * + * v15.1.1 created + * @param {...(Array|Matrix)} x One or more matrices or arrays + * @return {Array} A vector with the broadcasted size. + */ + return typed(name, { + '...Array|Matrix': collections => broadcastSizes(...collections.map(collection => collection.valueOf())) + }) +}) diff --git a/src/function/matrix/broadcastTo.js b/src/function/matrix/broadcastTo.js new file mode 100644 index 0000000000..c90a253d52 --- /dev/null +++ b/src/function/matrix/broadcastTo.js @@ -0,0 +1,43 @@ +import { broadcastTo } from '../../utils/array.js' +import { factory } from '../../utils/factory.js' + +const name = 'broadcastTo' +const dependencies = ['typed'] + +export const createBroadcastTo = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => { + /** + * Broadcast an array to a specified size. + * + * Syntax: + * + * math.broadcastTo(x, size) + * + * Examples: + * + * math.broadcastTo([1, 2, 3], [2, 3]) // returns [[1, 2, 3], [1, 2, 3]] + * math.broadcastTo([2, 3], [2, 2]) // returns [[2, 3], [2, 3]] + * + * See also: + * + * size, reshape, squeeze, broadcastSizes + * + * History: + * + * v15.1.1 created + * + * @param {Array|Matrix} x The array or matrix to broadcast + * @param {Array|Matrix} size The target size + * @return {Array} The broadcasted array + */ + return typed(name, { + 'Array, Array': broadcastTo, + 'Array, Matrix': (arr, size) => broadcastTo(arr, size.valueOf()), + 'Matrix, Array|Matrix': (M, size) => { + const result = M.create() + result._size = size.valueOf() + result._data = broadcastTo(M.valueOf(), size.valueOf()) + result._datatype = M.datatype() + return result + } + }) +}) diff --git a/src/utils/array.js b/src/utils/array.js index 3c8b8813fa..4097372fa3 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -810,7 +810,7 @@ export function broadcastArrays (...arrays) { throw new Error('Insufficient number of arguments in function broadcastArrays') } if (arrays.length === 1) { - return arrays[0] + return arrays } const sizes = arrays.map(function (array) { return arraySize(array) }) const broadcastedSize = broadcastSizes(...sizes) diff --git a/test/unit-tests/function/matrix/broadcastMatrices.test.js b/test/unit-tests/function/matrix/broadcastMatrices.test.js new file mode 100644 index 0000000000..c870c9d531 --- /dev/null +++ b/test/unit-tests/function/matrix/broadcastMatrices.test.js @@ -0,0 +1,35 @@ +import assert from 'assert' +import math from '../../../../src/defaultInstance.js' + +describe('broadcastMatrices', function () { + const matrix = math.matrix + const broadcastMatrices = math.broadcastMatrices + const A = [[1], [2], [3]] + const B = [[10, 20, 30]] + const C = [100] + const broadcastedA = [[1, 1, 1], [2, 2, 2], [3, 3, 3]] + const broadcastedB = [[10, 20, 30], [10, 20, 30], [10, 20, 30]] + const broadcastedC = [[100, 100, 100], [100, 100, 100], [100, 100, 100]] + + it('should broadcast matrices', function () { + assert.deepStrictEqual(broadcastMatrices(matrix(A), matrix(B)), [matrix(broadcastedA), matrix(broadcastedB)]) + assert.deepStrictEqual(broadcastMatrices(matrix(A), matrix(C)), [matrix(A), matrix([[100], [100], [100]])]) + assert.deepStrictEqual(broadcastMatrices(matrix(B), matrix(A)), [matrix(broadcastedB), matrix(broadcastedA)]) + assert.deepStrictEqual(broadcastMatrices(matrix(A), matrix(B), matrix(C)), [matrix(broadcastedA), matrix(broadcastedB), matrix(broadcastedC)]) + }) + + it('should broadcast arrays', function () { + assert.deepStrictEqual(broadcastMatrices(A, B), [broadcastedA, broadcastedB]) + assert.deepStrictEqual(broadcastMatrices(B, A), [broadcastedB, broadcastedA]) + assert.deepStrictEqual(broadcastMatrices(A, B, C), [broadcastedA, broadcastedB, broadcastedC]) + assert.deepStrictEqual(broadcastMatrices(A), [A]) + assert.deepStrictEqual(broadcastMatrices(B, C, A), [broadcastedB, broadcastedC, broadcastedA]) + }) + + it('should throw an error if sizes are not compatible', function () { + assert.throws(function () { broadcastMatrices(matrix([[1, 2], [3, 4]]), matrix([[1, 2, 3]])) }, /Error: shape mismatch: /) + assert.throws(function () { broadcastMatrices([[1, 2], [3, 4]], matrix([[1, 2, 3]])) }, /Error: shape mismatch: /) + assert.throws(function () { broadcastMatrices(matrix([[1, 2], [3, 4]]), [[1, 2, 3]]) }, /Error: shape mismatch: /) + assert.throws(function () { broadcastMatrices([[1, 2], [3, 4]], [[1, 2, 3]]) }, /Error: shape mismatch: /) + }) +}) diff --git a/test/unit-tests/function/matrix/broadcastSizes.test.js b/test/unit-tests/function/matrix/broadcastSizes.test.js new file mode 100644 index 0000000000..1978417daf --- /dev/null +++ b/test/unit-tests/function/matrix/broadcastSizes.test.js @@ -0,0 +1,27 @@ +import assert from 'assert' +import math from '../../../../src/defaultInstance.js' + +describe('broadcastSizes', function () { + const broadcastSizes = math.broadcastSizes + const matrix = math.matrix + + it('should broadcast sizes', function () { + assert.deepStrictEqual(broadcastSizes([2, 3]), [2, 3]) + assert.deepStrictEqual(broadcastSizes([3, 3], [3, 1]), [3, 3]) + assert.deepStrictEqual(broadcastSizes([2, 1], [1, 3]), [2, 3]) + assert.deepStrictEqual(broadcastSizes([5, 4, 3], [1, 4, 1]), [5, 4, 3]) + assert.deepStrictEqual(broadcastSizes([3], [2, 3]), [2, 3]) + assert.deepStrictEqual(broadcastSizes([1, 3], [2, 1]), [2, 3]) + }) + + it('should throw an error if sizes are not compatible', function () { + assert.throws(function () { broadcastSizes([2, 3], [3, 2]) }, /Error: shape mismatch: /) + assert.throws(function () { broadcastSizes([2, 3], [2, 3, 4]) }, /Error: shape mismatch: /) + }) + + it('should broadcast sizes of mixed arrays and matrices', function () { + assert.deepStrictEqual(broadcastSizes([3, 3], matrix([3, 1])), [3, 3]) + assert.deepStrictEqual(broadcastSizes(matrix([2, 1]), [1, 3]), [2, 3]) + assert.deepStrictEqual(broadcastSizes(matrix([5, 4, 3]), matrix([1, 4, 1])), [5, 4, 3]) + }) +}) diff --git a/test/unit-tests/function/matrix/broadcastTo.test.js b/test/unit-tests/function/matrix/broadcastTo.test.js new file mode 100644 index 0000000000..3498f7e915 --- /dev/null +++ b/test/unit-tests/function/matrix/broadcastTo.test.js @@ -0,0 +1,29 @@ +import assert from 'assert' +import math from '../../../../src/defaultInstance.js' + +describe('broadcastTo', function () { + const broadcastTo = math.broadcastTo + const matrix = math.matrix + + it('should broadcast arrays to a given size', function () { + assert.deepStrictEqual(broadcastTo([1, 2, 3], [2, 3]), [[1, 2, 3], [1, 2, 3]]) + assert.deepStrictEqual(broadcastTo([2, 3], [2, 2]), [[2, 3], [2, 3]]) + }) + + it('should broadcast matrices to a given size', function () { + assert.deepStrictEqual(broadcastTo(matrix([1, 2, 3]), [2, 3]), matrix([[1, 2, 3], [1, 2, 3]])) + assert.deepStrictEqual(broadcastTo(matrix([2, 3]), [2, 2]), matrix([[2, 3], [2, 3]])) + }) + + it('should broadcast mixed arrays and matrices to a given size', function () { + assert.deepStrictEqual(broadcastTo([1, 2, 3], matrix([2, 3])), [[1, 2, 3], [1, 2, 3]]) + assert.deepStrictEqual(broadcastTo(matrix([2, 3]), [2, 2]), matrix([[2, 3], [2, 3]])) + assert.deepStrictEqual(broadcastTo(matrix([1, 2, 3]), matrix([2, 3])), matrix([[1, 2, 3], [1, 2, 3]])) + assert.deepStrictEqual(broadcastTo([2, 3], matrix([2, 2])), [[2, 3], [2, 3]]) + }) + + it('should throw an error if sizes are not compatible', function () { + assert.throws(function () { broadcastTo([1, 2], [2, 3]) }, /Error: shape mismatch: /) + assert.throws(function () { broadcastTo(matrix([1, 2]), [2, 3]) }, /Error: shape mismatch: /) + }) +}) diff --git a/test/unit-tests/utils/array.test.js b/test/unit-tests/utils/array.test.js index f779379356..af3e3f8e71 100644 --- a/test/unit-tests/utils/array.test.js +++ b/test/unit-tests/utils/array.test.js @@ -701,19 +701,19 @@ describe('util.array', function () { assert.deepStrictEqual(broadcastArrays([1, 2], [[3], [4]], [5, 6]), [[[1, 2], [1, 2]], [[3, 3], [4, 4]], [[5, 6], [5, 6]]]) }) - it('should broadcast leave arrays as such when only one is supplied', function () { - assert.deepStrictEqual(broadcastArrays([1, 2]), [1, 2], [3, 4]) - assert.deepStrictEqual(broadcastArrays([[3], [4]]), [[3], [4]]) - assert.deepStrictEqual(broadcastArrays([[5, 6]]), [[5, 6]]) + it('should broadcast a single array, returning the array itself in an array', function () { + assert.deepStrictEqual(broadcastArrays([1, 2])[0], [1, 2]) + assert.deepStrictEqual(broadcastArrays([[3], [4]])[0], [[3], [4]]) + assert.deepStrictEqual(broadcastArrays([[5, 6]])[0], [[5, 6]]) }) - it('should throw an arryor when the broadcasting rules don\'t apply', function () { + it('should throw an error when the broadcasting rules don\'t apply', function () { assert.throws(function () { broadcastArrays([1, 2], [1, 2, 3]) }) assert.throws(function () { broadcastArrays([1, 2], [1, 2, 3], [4, 5]) }) assert.throws(function () { broadcastArrays([[1, 2], [1, 2]], [[1, 2, 3]]) }) }) - it('should throw an arryor when not enough arguments are supplied', function () { + it('should throw an error when not enough arguments are supplied', function () { assert.throws(function () { broadcastArrays() }) }) }) diff --git a/types/index.d.ts b/types/index.d.ts index 7900d3bb1c..746575fd17 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -2229,6 +2229,79 @@ export interface MathJsInstance extends MathJsFactory { > ): T + /** + * Broadcast a matrix or array to a specified size. + * + * The input collection is conceptually expanded to match the given dimensions, + * following broadcasting rules. The returned object is a new matrix or array + * with the requested size; the original input is not modified. + * + * @param x The matrix or array to broadcast. + * @param size The target size for each dimension of the broadcasted result. + * @returns A matrix or array with the specified size, containing broadcasted values. + * @param x The matrix or array to broadcast. + * @param size The target size of the broadcasted output, specified as an array of dimension lengths. + * @returns A new matrix or array with the requested size, containing the broadcasted values of `x`. + */ + broadcastTo( + x: Matrix, + size: number[] | BigNumber[] | Matrix | Matrix + ): Matrix + broadcastTo( + x: MathArray, + size: number[] | BigNumber[] | Matrix | Matrix + ): MathArray + + /** + * Calculate the broadcasted size of one or more matrices or arrays. + * Always returns an Array containing numbers. + * + * Syntax: + * + * math.broadcastSizes(x, y) + * math.broadcastSizes(x, y, ...) + * + * Examples: + * + * math.broadcastSizes([2, 3]) // returns [2, 3] + * math.broadcastSizes([2, 3], [3]) // returns [2, 3] + * math.broadcastSizes([1, 2, 3], [[1, 2, 3]]) // returns [1, 2, 3] + * + * See also: + * + * size, reshape, squeeze, broadcastTo + * + * @param {...(Array|Matrix)} x One or more matrices or arrays + * @return {number[]} A vector with the broadcasted size. + */ + broadcastSizes(...args: Array): number[] + + /** + * Broadcast multiple matrices together. + * Return an array of matrices with the broadcasted sizes. + * + * Syntax: + * + * math.broadcastMatrices(x, y) + * math.broadcastMatrices(x, y, ...) + * + * Examples: + * + * math.broadcastMatrices([1, 2], [[3], [4]]) // returns [[[1, 2], [1, 2]], [[3, 3], [4, 4]]] + * math.broadcastMatrices([2, 3]) // returns [[2, 3]] + * math.broadcastMatrices([2, 3], [3, 1]) // returns [[2, 3], [3, 1]] + * + * See also: + * + * size, reshape, broadcastSizes, broadcastTo + * + * @param {...(Array|Matrix)} args One or more matrices or arrays + * @returns {(Array|Matrix)[]} An array of matrices with the broadcasted sizes. + */ + broadcastMatrices( + ...args: Array + ): Array + /** * Create a matrix filled with ones. The created matrix can have one or * multiple dimensions.