I think one aspect that would be hard to think about is how the tiling
would happen when broadcasting from K -> N where N/K is an integer. There
are at least 2 different ways to tile that would produce different results.
Suppose you have the array

[[1, 2, 3]]

which is (1,3).  If you wanted to broadcast to (1,9), you could want

[[1,1,1,2,2,2,3,3,3]] or [[1,2,3,1,2,3,1,2,3]]

This ambiguity doesn't arise with broadcasting from 1->N. Probably better
to allow users to manually tile in this case.

Kevin


On Tue, Mar 25, 2025 at 1:31 PM Shasang Thummar via NumPy-Discussion <
numpy-discussion@python.org> wrote:

> Dear NumPy Developers,
>
>
> I hope you are doing well. I am writing to propose an enhancement to NumPy’s 
> broadcasting mechanism that could make the library even more powerful, 
> intuitive, and flexible while maintaining its memory efficiency.
>
> ## **Current Broadcasting Rule and Its Limitation**
> As per NumPy's current broadcasting rules, if two arrays have different 
> shapes, the smaller array can be expanded **only if one of its dimensions is 
> 1**. This allows memory-efficient expansion of data without copying. However, 
> if a dimension is greater than 1, NumPy **does not** allow expansion to a 
> larger size, even when the larger size is a multiple of the smaller size.
>
> ### **Example of Current Behavior (Allowed Expansion)**
> ```python
> import numpy as np
>
> A = np.array([[1, 2, 3]])  # Shape (1,3)
> B = np.array([[4, 5, 6],    # Shape (2,3)
>               [7, 8, 9]])
>
> C = A + B  # Broadcasting works because (1,3) can expand to (2,3)
> print(C)
>
> *Output:*
>
> [[ 5  7  9]
>  [ 8 10 12]]
>
> Here, A has shape (1,3), which is automatically expanded to (2,3) without
> copying because *a dimension of size 1 can be stretched*.
>
> However, *NumPy fails when trying to expand a dimension where N > 1, even
> if the larger size is a multiple of N*.
> *Example of a Case That Fails (Even Though It Could Work)*
>
> A = np.array([[1, 2, 3],    # Shape (2,3)
>               [4, 5, 6]])
>
> B = np.array([[10, 20, 30],  # Shape (4,3)
>               [40, 50, 60],
>               [70, 80, 90],
>               [100, 110, 120]])
>
> C = A + B  # Error! NumPy does not allow (2,3) to (4,3)
>
> *This fails with the error:*
>
> ValueError: operands could not be broadcast together with shapes (2,3) (4,3)
>
> *Why Should This Be Allowed?*
>
> If *a larger dimension is an exact multiple of a smaller one*, then
> logically, the smaller array can be *repeated* along that axis *without
> physically copying data*—just like NumPy does when broadcasting (1,M) to
> (N,M).
>
> In the above example,
>
>    - A has shape (2,3), and B has shape (4,3).
>    - Since 4 is *a multiple of* 2 (4 % 2 == 0), A could be *logically
>    repeated 2 times* to become (4,3).
>    - NumPy *already does* a similar expansion when a dimension is 1, so
>    why not extend the same logic?
>
> *Proposed Behavior (Expanding N → M When M % N == 0)*
>
> Allow an axis with size N to be broadcast to size M *if and only if M is
> an exact multiple of N (M % N == 0)*. This is *just as memory-efficient*
> as the current broadcasting rules because it can be done using *stride
> tricks instead of copying data*.
> *Example of the Proposed Behavior*
>
> If NumPy allowed this new form of broadcasting:
>
> A = np.array([[1, 2, 3],    # Shape (2,3)
>               [4, 5, 6]])
>
> B = np.array([[10, 20, 30],  # Shape (4,3)
>               [40, 50, 60],
>               [70, 80, 90],
>               [100, 110, 120]])
>
> # Proposed new broadcasting rule
> C = A + B
>
> print(C)
>
> *Expected Output:*
>
> [[ 11  22  33]
>  [ 44  55  66]
>  [ 71  82  93]
>  [104 115 126]]
>
> This works by *logically repeating A* to match B’s shape (4,3).
> *Why This is a Natural Extension of Broadcasting*
>
>    - *Memory Efficiency:* Just like broadcasting (1,M) to (N,M), this
>    expansion does *not* require physically copying data. Instead, strides
>    can be adjusted to *logically repeat elements*, making this as
>    efficient as current broadcasting.
>    - *Intuitiveness:* Right now, broadcasting already surprises new
>    users. If (1,3) can become (2,3), why not (2,3) to (4,3) when 4 is a
>    multiple of 2? This would be a more intuitive rule.
>    - *Extends Current Functionality:* This is *not* a new concept—it *extends
>    the existing rule* where 1 can be stretched to any value. We are
>    generalizing it to *any factor relationship* (N → M when M % N == 0).
>
> *Implementation Considerations*
>
> The logic behind NumPy’s current broadcasting already uses *stride tricks*
> for memory-efficient expansion. Extending it to handle (N, M) → (K, M)
> (where K % N == 0) would require:
>
>    - Updating np.broadcast_shapes(), np.broadcast_to(), and related
>    functions.
>    - Extending the existing logic that handles expanding 1 to support
>    factors as well.
>    - Ensuring backward compatibility and maintaining performance.
>
> *Conclusion*
>
> I strongly believe this enhancement would make NumPy’s broadcasting *more
> powerful, more intuitive, and just as efficient as before*. The fact that
> we can implement this *without unnecessary copying* makes it a natural
> extension of the existing feature.
>
> I would love to hear the NumPy team’s thoughts on this! If the community
> is open to it, I am also interested in contributing to its implementation.
>
> Looking forward to your feedback!
>
> Best regards,
> *Shasang Thummar*
> shasangthumm...@gmail.com
>
>  _______________________________________________
> NumPy-Discussion mailing list -- numpy-discussion@python.org
> To unsubscribe send an email to numpy-discussion-le...@python.org
> https://mail.python.org/mailman3/lists/numpy-discussion.python.org/
> Member address: kevin.k.shepp...@gmail.com
>
_______________________________________________
NumPy-Discussion mailing list -- numpy-discussion@python.org
To unsubscribe send an email to numpy-discussion-le...@python.org
https://mail.python.org/mailman3/lists/numpy-discussion.python.org/
Member address: arch...@mail-archive.com

Reply via email to