Add bcast and bcast_until_actx_array functions - #307
Conversation
inducer
left a comment
There was a problem hiding this comment.
Some specific questions for the review.
| (k, op(left_v, right)) for k, left_v in serialized]) | ||
| def bcast_left_until_actx_array( |
There was a problem hiding this comment.
Naming? (May not matter as much, as it's convenient to make local aliases, as in the grudge PR.)
There was a problem hiding this comment.
Maybe rec_bcast_left? Mostly to match some of the other traversal functions.
I agree that local aliases are probably needed, so this should be nice and verbose.. i.e. I'm quite fine with the name.
There was a problem hiding this comment.
Agree with adding rec_, and I would suggest maybe adding something else to disambiguate what's happening to the "left". (Picturing myself looking at this again in 6 months and not being sure if it means "broadcast the left across" or "broadcast across the left".) rec_bcast_left_operand_across_actx_arrays or something (probably too verbose, maybe you can think of something better).
| (k, op(left_v, right)) for k, left_v in serialized]) | ||
| def bcast_left_until_actx_array( |
There was a problem hiding this comment.
Argument order? (Here and above.) I initially had the operator in the middle but went with "first" to allow convenient use of partial.
There was a problem hiding this comment.
I like the functional feel of it too 👍
| # }}} | ||
| # {{{ |
There was a problem hiding this comment.
| # {{{ | |
| # {{{ bcast |
Uh oh!
There was an error while loading. Please reload this page.
alexfikl
left a comment
There was a problem hiding this comment.
Took a quick look and it seems fine to me.
The only issue would be if it's too verbose for more complex usage? The one in grudge looks nice enough to me :\
inducer
commented
Mar 28, 2025
Thanks for taking a look!
Same.
Good question. Without question, the prior state in grudge is nicer to look at. Before: After: On the one hand, it's clearly more verbose. On the other, the verbosity scales linearly with expression size, so maybe not too bad? We could also just defer this issue to when it comes up. One attractive feature of these functions is that they're easy to replace: deprecate, grep, done. So I anticipate this to be low regret, even if it turns out to not be the right answer long term. If we wanted to preserve the operatory-y feel, we need a way to let the the operator know broadcast rules (given that they depend on the array context), and we are (I am) hoping to no longer have array context handles in array containers. The only approach I can think of there is the (That's assuming a local alias |
alexfikl
commented
Mar 28, 2025
That's a good point.. I forgot that version was quite verbose-y as well. Then yeah, from my side this looks very nice and very low on the black-box magic compared to the previous version 😁 |
| (k, op(left_v, right)) for k, left_v in serialized]) | ||
| def bcast_left_until_actx_array( |
There was a problem hiding this comment.
Agree with adding rec_, and I would suggest maybe adding something else to disambiguate what's happening to the "left". (Picturing myself looking at this again in 6 months and not being sure if it means "broadcast the left across" or "broadcast across the left".) rec_bcast_left_operand_across_actx_arrays or something (probably too verbose, maybe you can think of something better).
| actx: ArrayContext, | ||
| op: Callable[[ArrayOrArithContainer, ArrayOrArithContainer], | ||
| ArrayOrArithContainer], | ||
| left: ArrayOrArithContainer, | ||
| right: ArithArrayContainer, | ||
| ) -> ArrayOrArithContainer: |
There was a problem hiding this comment.
Would it make sense to have the interface for these be more like the container traversal functions, e.g.:
defrec_bcast_left(op, left, right, leaf_cls: type|None=None):
...and then
mul=partial(rec_bcast_left, operator.mul, leaf_cls=actx.array_types)?
majosm
commented
Mar 28, 2025
TBH from a syntax point of view I think I like the |
Does the Edit: I'm imagining something roughly along the lines of |
inducer
commented
Mar 28, 2025
Good question. If we wrap the thing being broadcast in However, if we wrap the container being broadcast over in |
majosm
commented
Mar 28, 2025
I can't speak for Alex, but I would object to that. 🙂 I would be confused by that syntax. I'm probably missing something obvious, but why wouldn't we be able to do something along the lines of: classBcast:
def__init__(self, array):
self.array=arraydef_binary_op(self, op, right):
# Need a reverse case too, but I'm lazytry:
serialized=serialize_container(right)
exceptNotAnArrayContainerError:
returnop(self.array, right)
returndeserialize_container(right, [
(k, op(self.array, right_v)
ifisinstance(right_v, actx.array_types) elseself._binary_op(op, right_v)
)
fork, right_vinserialized])
__mul__=partialmethod(_binary_op, operator.mul)? |
inducer
commented
Mar 28, 2025
I'm not sure you are. That looks possible from my point of view. In #280, I think I was too mentally focused on modifying code in the container arithmetic, leading to loads of unnecessary complexity. |
inducer
commented
Mar 28, 2025
To add: I'm not hating that as a possible direction. It shouldn't need any weird "magic". @alexfikl? |
alexfikl
commented
Mar 28, 2025
Agreed! That looks like it should be pretty easy to understand with some of the same greppability. I'll mull it over, but can't think of any downsides at the moment :-? |
inducer
commented
Mar 31, 2025
See #310 for a stab at this. |
I'm mostly seeking early feedback on this. It still needs, at least:
This could help fill the hole that's left by the plan to make implicit broadcasting of data class containers across actx array types illegal. See inducer/grudge#377 for an example use, specifically
shortcuts.py.Closes#280. (Would supersede the cursed
Bcastobjects that @alexfikl hated---and I have to agree, it is just nicer.)