-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Broadcasting functions #3624
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Broadcasting functions #3624
Changes from all commits
6b0ea89
cd83def
f2a2943
756aa83
a804f9d
cfe6490
5224a45
b212e6f
4315651
c99c48a
d749405
c267a8c
23bc583
26367b5
54338f5
5e13b30
d593869
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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' | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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' | ||
| ] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| export const broadcastToDocs = { | ||
| name: 'broadcastTo', | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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,
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
I don't have a strong opinion on this, just please review if it makes sense to follow that convention.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| 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' | ||
| ] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
| } | ||
| }) | ||
| }) |
| 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. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 contents 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. |
||
| * 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())) | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. | ||
| * | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Basically the same documentation comments: needs to be clear (how to find out) what broadcasting is, and there should be mention of what happens with incompatible size. Presumably that also means that (for all three functions) there should be an example with incompatible 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 | ||
| } | ||
| }) | ||
| }) | ||
| 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: /) | ||
| }) | ||
| }) |
| 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]) | ||
| }) | ||
| }) |
| 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: /) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, I am concerned about at least the grammatical correctness here. It's not that any sizes are being broadcast here, per se, is it, but rather that the size resulting from a broadcast is being computed, right? So shouldn't the description be something more like "Compute the size that would result from broadcasting a list of matrices of the given sizes, if possible"? (Again, this function can throw an error if the sizes are incompatible, correct?)
This also observation also, for me, calls into question the name of the function. Would
broadcastSizeorsizeOfBroadcastbe more descriptive, again since no sizes are being broadcast, per se?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The terminology used by numpy is
I think I understand where are you coming from, because the original sizes are kept intact. But maybe it's an implicit definition, because when one adds numbers, nothing happens to the numbers, we could say is to compute the result from adding numbers.
Yes this function will throw an error for incompatible sizes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I meant that it's the matrices that are broadcast, not their sizes. This function does not actually do any broadcasting. It just computes a size, and so it should be named accordingly, I think. Your thoughts?