Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/expression/embeddedDocs/embeddedDocs.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,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'
Expand Down Expand Up @@ -485,6 +488,9 @@ export const embeddedDocs = {
partitionSelect: partitionSelectDocs,
range: rangeDocs,
resize: resizeDocs,
broadcastMatrices: broadcastMatricesDocs,
broadcastTo: broadcastToDocs,
broadcastSizes: broadcastSizesDocs,
reshape: reshapeDocs,
rotate: rotateDocs,
rotationMatrix: rotationMatrixDocs,
Expand Down
17 changes: 17 additions & 0 deletions src/expression/embeddedDocs/function/matrix/broadcastMatrices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const broadcastMatricesDocs = {
name: 'broadcastMatrices',
category: 'Matrix',
syntax: [
'broadcastMatrices(A, B)',
'broadcastMatrices(A, B, ...)'
],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the syntax read 'broadcastMatrices(A, B, ...)' since any number of arguments are allowed?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, yes you are right. Fixed

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'
]
}
17 changes: 17 additions & 0 deletions src/expression/embeddedDocs/function/matrix/broadcastSizes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const broadcastSizesDocs = {
name: 'broadcastSizes',
category: 'Matrix',
syntax: [
'broadcastSizes(sizeA, sizeB)',
'broadcastSizes(sizeA, sizeB, ...)'
],
description: 'Broadcast the sizes of matrices to a compatible size. Computes the size of the resulting matrix when broadcasting the input sizes against each other.',
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'
]
}
15 changes: 15 additions & 0 deletions src/expression/embeddedDocs/function/matrix/broadcastTo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const broadcastToDocs = {
name: 'broadcastTo',
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I worry about the name of this function. It seems to me that since sizes look like matrices, visually, broadcastTo([3], [2, 2]) could look like it is supposed to broadcast the first matrix to be compatible with the second, i.e. produce [3,3] rather than [3, 3; 3, 3]. I would strongly recommend considering renaming the function to broadcastToSize([3], [2, 2]) to avoid this ambiguity.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand. Many of these are taken from numpy and have counterparts in jax / mlx / pytorch and maybe others.

numpy.broadcast_to(array, shape, subok=False)
Broadcast an array to a new shape.

I don't have a strong opinion on this, just please review if it makes sense to follow that convention.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am the one less familiar with the territory here. That's why this was couched as a suggestion. Please select the name you think is best, including leaving it be, unless @josdejong weighs in otherwise. Please just post your final decision here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, to land this PR we need a decision on the final name here. If you are on the fence, I recommend switching to broadcastToSize() in an effort to steer away from the ambiguity I raised. It seems to me a more fully specified name can't be harmful here. (And I don't think we need to worry too much about fidelity to numpy names, since after all to begin with what they call a "shape" we call a "size" so we're not adopting numpy terminology right from the start.)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree about the differences between shape and size. I'm considering broadcastToSize(), will review and resolve this comment.

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'
]
}
3 changes: 3 additions & 0 deletions src/factoriesAny.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
46 changes: 46 additions & 0 deletions src/function/matrix/broadcastMatrices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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.
* The broadcasting rules can be found in [Matrices#Broadcasting](./datatype/matrices#Broadcasting).
*
* 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)
}
})
})
36 changes: 36 additions & 0 deletions src/function/matrix/broadcastSizes.js
Original file line number Diff line number Diff line change
@@ -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.
Copy link
Copy Markdown
Collaborator

@gwhitney gwhitney Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per my comments on the internal docs, shouldn't this be something more like "Calculate the size that would result from broadcasting one or more matrices or arrays, given the sizes of the input collections."?

The same comments about having documentation on the operation of broadcasting either here or linked here apply to this function as well. Also mention of what happens with incompatible sizes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still needs further editing/documentation.

* Always returns an Array containing numbers.
* The broadcasting rules can be found in [Matrices#Broadcasting](./datatype/matrices#Broadcasting).*
* 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()))
})
})
44 changes: 44 additions & 0 deletions src/function/matrix/broadcastTo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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.
* The broadcasting rules can be found in [Matrices#Broadcasting](./datatype/matrices#Broadcasting).
*
* 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': function (M, size) {
return M.create({
data: broadcastTo(M.valueOf(), size.valueOf()),
size: size.valueOf()
},
M.datatype())
}
})
})
2 changes: 1 addition & 1 deletion src/utils/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
35 changes: 35 additions & 0 deletions test/unit-tests/function/matrix/broadcastMatrices.test.js
Original file line number Diff line number Diff line change
@@ -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: /)
})
})
27 changes: 27 additions & 0 deletions test/unit-tests/function/matrix/broadcastSizes.test.js
Original file line number Diff line number Diff line change
@@ -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])
})
})
29 changes: 29 additions & 0 deletions test/unit-tests/function/matrix/broadcastTo.test.js
Original file line number Diff line number Diff line change
@@ -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: /)
})
})
12 changes: 6 additions & 6 deletions test/unit-tests/utils/array.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() })
})
})
Expand Down
Loading
Loading