Skip to content

Broadcasting functions#3624

Open
dvd101x wants to merge 17 commits intodevelopfrom
broadcasting-functions
Open

Broadcasting functions#3624
dvd101x wants to merge 17 commits intodevelopfrom
broadcasting-functions

Conversation

@dvd101x
Copy link
Collaborator

@dvd101x dvd101x commented Dec 26, 2025

Hi this is according to discussion #3516

It's missing some tests and type checking.

@gwhitney
Copy link
Collaborator

OK, I will convert to draft and you can mark it as ready for review when you feel you've supplied all the missing bits.

@gwhitney gwhitney marked this pull request as draft December 27, 2025 22:50
@dvd101x dvd101x marked this pull request as ready for review December 28, 2025 20:28
@dvd101x
Copy link
Collaborator Author

dvd101x commented Dec 29, 2025

Hi, this is ready for review.

Copy link
Collaborator

@gwhitney gwhitney left a comment

Choose a reason for hiding this comment

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

I can't comment on the utility/advisability of exposing these functions in the top-level user interface of mathjs,but I am assuming that's something you've already worked out with Jos. So I am explicitly not attempting to judge whether these functions should be added. Presuming they should be, here's my review of the PR to add them.

syntax: [
'broadcastMatrices(A, B)'
],
description: 'Broadcast two matrices to compatible sizes',
Copy link
Collaborator

Choose a reason for hiding this comment

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

This description doesn't seem quite correct, or at least not grammatically correct. If I am understanding correctly:

  • broadcastMatrices can take any positive number of arguments, not just two
  • There is just one "smallest mutually compatible size" that it broadcasts every argument to.
  • Correct me if I am wrong, but this may fail for incompatible sizes, like there is no way to broadcast a 2×3 matrix and a 3×2 matrix, correct?

So maybe something like: "Broadcast a list of matrices to their smallest compatible size (if any)"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks,

  • yes, any number of arguments, will fix.
  • I think there is only one, at least for the current definition.
  • yes, will fail for incompatible sizes.

The general terminology used is dominated by numpy with:

Broadcast any number of arrays against each other.

It could be

Broadcast any number of arrays or matrices against each other.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Your final suggestion seems fine to me.

syntax: [
'broadcastSizes(sizeA, sizeB)'
],
description: 'Broadcast the sizes of matrices to a compatible size',
Copy link
Collaborator

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 broadcastSize or sizeOfBroadcast be more descriptive, again since no sizes are being broadcast, per se?

Copy link
Collaborator Author

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

numpy.broadcast_shapes(*args)
Broadcast the input shapes into a single shape.

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.

Copy link
Collaborator

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?

@@ -0,0 +1,15 @@
export const broadcastToDocs = {
name: 'broadcastTo',
Copy link
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
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
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.

export const createBroadcastMatrices = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => {
/**
* Broadcast multiple matrices together.
* Return and array of matrices with the broadcasted sizes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Typo: "and" -> "an"

This documentation is way too terse for someone who's not already familiar with the operation of broadcasting matrices (which is not necessarily all that common or standard) to understand what is going on. Somewhere in the documentation needs to be a careful documentation from the ground up with examples what it means to broadcast two or more matrices. That could be here, or it could be elsewhere (like in the general matrix documentation page) and then be linked to here. Such documentation might already exist, and then all you need is a link.

This documentation should say what happens with incompatible sizes.

Finally, you have "sizes" plural. But isn't it the case that there is only one common size produced by broadcasting a list of matrices?


export const createBroadcastSizes = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => {
/**
* Calculate the broadcasted size of one or more matrices or arrays.
Copy link
Collaborator

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 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.

@@ -702,9 +702,9 @@ describe('util.array', function () {
})

it('should broadcast leave arrays as such when only one is supplied', function () {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I know you didn't create these problems, but there are typos/ungrammaticality in the labels of both this test and the following one. Please fix.

/**
* Broadcast a matrix or array to a specified size.
*
* The input collection is conceptually expanded to match the given dimensions,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe instead "entries of the input collection are duplicated to match the given 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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Where do I find these "broadcasting rules"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good question, I don't think broadcasting is described with specific rules, the chapter can be found at broadcasting.

The best source I've found is from numpy.
https://numpy.org/doc/stable/user/basics.broadcasting.html
there is one from octave
https://docs.octave.org/latest/Broadcasting.html#Broadcasting-1

I personally didn't know about this topic until a few years ago even after using Matlab/Octave extensively. The links I'm sharing is not an assumption of anyone's knowledge, just sharing them to try to answer the question.

I don't know what would be best, extend the chapter, have a better phrasing of "broadcasting rules" or something else.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Good question, I don't think broadcasting is described with specific rules, the chapter can be found at broadcasting.

All good. Just make that phrase "broadcasting rules" a link to that spot in the on-line docs, and make any fixes/additions you deem valuable to that section on broadcasting (for example, at least the first example needs to be corrected, as [1,2] + 3 = [4,5], not [3,4] as shown). Then all will be well. Similaly, the doc sections in the broadcast functions themselves should link to that broadcasting link. Thanks!

const arrays = collections.map((c, i) => areMatrices[i] ? c.valueOf() : c)
return broadcastSizes(...arrays)
}
return broadcastSizes(...collections)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is precomputing areMatrices and conditioning on whether any entry is true really worth it as compared to the much simpler-looking return broadcastSizes(...collections.map(c => c.valueOf())) ? [Note that it is perfectly ok to call .valueOf on an Array, it's just a no-op.]

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I haven't checked the performance implications, I didn't consider running .valueOf() on an array.

I will do a quick check but I think you are right. Will change in code.

'Array, Array': broadcastTo,
'Array, Matrix': (arr, size) => broadcastTo(arr, size.toArray()),
'Matrix, Array': (M, size) => M.create(broadcastTo(M.toArray(), size)),
'Matrix, Matrix': (M1, size) => M1.create(broadcastTo(M1.toArray(), size.toArray()))
Copy link
Collaborator

Choose a reason for hiding this comment

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

  • Why are we using toArray() here rather than valueOf() as in the other new functions' implementation?
  • Why does the matrix implementation use M1 rather than M?
  • Consider merging the second two:
`Matrix, Array | Matrix`: (M, size) => M.create(broadcastTo(M.valueOf(), size.valueOf())`

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, will review and fix in code.

@dvd101x
Copy link
Collaborator Author

dvd101x commented Jan 4, 2026

... but I am assuming that's something you've already worked out with Jos.

Yes. I took this comment as an OK.

#3516 (reply in thread)

Part of the argument is that these are exposed by numpy even if broadcasting is deeply integrated. Also during the implementation of broadcasting there were some discussions about the specific functions.

#2753 (comment)

#2895

I think this means it's ok, but if not please let me know.

@gwhitney
Copy link
Collaborator

gwhitney commented Jan 4, 2026

I took this comment as an OK.

Yes, you convinced (an initially skeptical) Jos so all OK :)

@josdejong
Copy link
Owner

Glen, thanks for reviewing the work of David.

I indeed think it's a good idea to add these functions.

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/bradcastTo.js'
Copy link
Owner

Choose a reason for hiding this comment

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

I think this filename bradcastTo has a typo and should be broadcastTo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants