Skip to content

Commit 7a52728

Browse files
task: add broadcast class implementation (#2901)
Adds a `broadcast` class implementation
1 parent bd0da1f commit 7a52728

8 files changed

Lines changed: 492 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ This release is compatible with NumPy 2.5.
1818
* Added implementation of `dpnp.lib.stride_tricks.as_strided` [#2991](https://github.com/IntelPython/dpnp/pull/2991)
1919
* Added `dpnp.tensor.broadcast_shapes` to align with the 2025.12 version of the Python array API [#3009](https://github.com/IntelPython/dpnp/pull/3009)
2020
* Added support for free-threaded Python builds [gh-3026](https://github.com/IntelPython/dpnp/pull/3026)
21+
* Added `dpnp.broadcast` class implementation [#2901](https://github.com/IntelPython/dpnp/pull/2901)
2122

2223
### Changed
2324

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{% extends "!autosummary/class.rst" %}
2+
3+
{% block methods %}
4+
{% if methods %}
5+
.. HACK -- the point here is that we don't want this to appear in the output, but the autosummary should still generate the pages.
6+
.. autosummary::
7+
:toctree:
8+
{% for item in all_methods %}
9+
{%- if not item.startswith('_') or item in ['__call__'] %}
10+
{{ name }}.{{ item }}
11+
{%- endif -%}
12+
{%- endfor %}
13+
{% endif %}
14+
{% endblock %}
15+
16+
{% block attributes %}
17+
{% if attributes %}
18+
.. rubric:: {{ _('Attributes') }}
19+
20+
.. autosummary::
21+
:toctree:
22+
{% for item in all_attributes %}
23+
{%- if not item.startswith('_') %}
24+
~{{ name }}.{{ item }}
25+
{%- endif -%}
26+
{%- endfor %}
27+
{% endif %}
28+
{% endblock %}

doc/known_words.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Nj
6969
Nk
7070
normed
7171
nuc
72+
numiter
7273
numpy
7374
nx
7475
ny

doc/reference/array-manipulation.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,20 @@ Transpose-like operations
5050
Changing number of dimensions
5151
-----------------------------
5252

53+
.. autosummary::
54+
:toctree: generated/
55+
:nosignatures:
56+
:template: autosummary/class_with_attributes.rst
57+
58+
broadcast
59+
5360
.. autosummary::
5461
:toctree: generated/
5562
:nosignatures:
5663

5764
atleast_1d
5865
atleast_2d
5966
atleast_3d
60-
broadcast
6167
broadcast_to
6268
broadcast_arrays
6369
expand_dims

dpnp/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@
189189
atleast_1d,
190190
atleast_2d,
191191
atleast_3d,
192+
broadcast,
192193
broadcast_arrays,
193194
broadcast_to,
194195
column_stack,
@@ -691,6 +692,7 @@
691692
"atleast_1d",
692693
"atleast_2d",
693694
"atleast_3d",
695+
"broadcast",
694696
"broadcast_arrays",
695697
"broadcast_to",
696698
"column_stack",

dpnp/dpnp_iface_manipulation.py

Lines changed: 196 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
from .dpnp_utils import get_usm_allocations
5757
from .dpnp_utils.dpnp_utils_pad import dpnp_pad
5858
from .exceptions import AxisError
59+
from .tensor._manipulation_functions import _broadcast_shapes
5960
from .tensor._numpy_helper import (
6061
normalize_axis_index,
6162
normalize_axis_tuple,
@@ -1047,6 +1048,193 @@ def atleast_3d(*arys):
10471048
return tuple(res)
10481049

10491050

1051+
class broadcast: # pylint: disable=invalid-name
1052+
"""
1053+
Produce an object that mimics broadcasting.
1054+
1055+
For full documentation refer to :obj:`numpy.broadcast`.
1056+
1057+
Parameters
1058+
----------
1059+
*args : {dpnp.ndarray, usm_ndarray}
1060+
Input arrays to broadcast against one another.
1061+
1062+
Returns
1063+
-------
1064+
broadcast : broadcast object
1065+
Broadcast the input parameters against one another, and
1066+
return an object that encapsulates the result.
1067+
Amongst others, it has ``shape`` and ``ndim`` properties.
1068+
1069+
Limitations
1070+
-----------
1071+
Input arrays are not coerced, so array-like objects and scalars are not
1072+
supported and ``TypeError`` exception will be raised.
1073+
1074+
See Also
1075+
--------
1076+
:obj:`dpnp.broadcast_arrays` : Broadcast any number of arrays against
1077+
each other.
1078+
:obj:`dpnp.broadcast_shapes` : Broadcast the input shapes into a single
1079+
shape.
1080+
:obj:`dpnp.broadcast_to` : Broadcast an array to a new shape.
1081+
1082+
Notes
1083+
-----
1084+
Iterator functionality is not supported.
1085+
1086+
The legacy ``nd`` attribute of :obj:`numpy.broadcast` is not provided,
1087+
``ndim`` has to be used instead.
1088+
1089+
Examples
1090+
--------
1091+
>>> import dpnp as np
1092+
>>> x = np.array([[1], [2], [3]])
1093+
>>> y = np.array([4, 5, 6])
1094+
>>> b = np.broadcast(x, y)
1095+
>>> b.shape
1096+
(3, 3)
1097+
>>> b.ndim
1098+
2
1099+
>>> b.size
1100+
9
1101+
1102+
"""
1103+
1104+
def __init__(self, *args):
1105+
dpnp.check_supported_arrays_type(*args)
1106+
1107+
self._arrays = args
1108+
self._values = None
1109+
1110+
# _broadcast_shapes() does not accept an empty sequence of arrays
1111+
self._shape = _broadcast_shapes(*args) if args else ()
1112+
self._size = math.prod(self._shape)
1113+
self._ndim = len(self._shape)
1114+
1115+
@property
1116+
def shape(self):
1117+
"""
1118+
Shape of the broadcasted result.
1119+
1120+
Returns
1121+
-------
1122+
out : tuple
1123+
A tuple containing the shape of the broadcasted result.
1124+
1125+
Examples
1126+
--------
1127+
>>> import dpnp as np
1128+
>>> x = np.array([[1], [2], [3]])
1129+
>>> y = np.array([4, 5, 6])
1130+
>>> np.broadcast(x, y).shape
1131+
(3, 3)
1132+
1133+
"""
1134+
return self._shape
1135+
1136+
@property
1137+
def size(self):
1138+
"""
1139+
Total size of the broadcasted result.
1140+
1141+
Returns
1142+
-------
1143+
out : int
1144+
The total size (number of elements) of the broadcasted result.
1145+
1146+
Examples
1147+
--------
1148+
>>> import dpnp as np
1149+
>>> x = np.array([[1], [2], [3]])
1150+
>>> y = np.array([4, 5, 6])
1151+
>>> np.broadcast(x, y).size
1152+
9
1153+
1154+
"""
1155+
return self._size
1156+
1157+
@property
1158+
def ndim(self):
1159+
"""
1160+
Number of dimensions of the broadcasted result.
1161+
1162+
Returns
1163+
-------
1164+
out : int
1165+
The number of dimensions of the broadcasted result.
1166+
1167+
Examples
1168+
--------
1169+
>>> import dpnp as np
1170+
>>> x = np.array([[1], [2], [3]])
1171+
>>> y = np.array([4, 5, 6])
1172+
>>> np.broadcast(x, y).ndim
1173+
2
1174+
1175+
"""
1176+
return self._ndim
1177+
1178+
@property
1179+
def numiter(self):
1180+
"""
1181+
Number of iterators possessed by the broadcast object.
1182+
1183+
Returns
1184+
-------
1185+
out : int
1186+
The number of iterators.
1187+
1188+
Examples
1189+
--------
1190+
>>> import dpnp as np
1191+
>>> x = np.array([[1], [2], [3]])
1192+
>>> y = np.array([4, 5, 6])
1193+
>>> np.broadcast(x, y).numiter
1194+
2
1195+
1196+
"""
1197+
return len(self._arrays)
1198+
1199+
@property
1200+
def values(self):
1201+
"""
1202+
The input arrays broadcast against one another.
1203+
1204+
Returns
1205+
-------
1206+
out : tuple of dpnp.ndarray
1207+
A tuple of arrays which are views on the original input arrays.
1208+
1209+
Examples
1210+
--------
1211+
>>> import dpnp as np
1212+
>>> x = np.array([[1], [2], [3]])
1213+
>>> y = np.array([4, 5, 6])
1214+
>>> b = np.broadcast(x, y)
1215+
>>> b.values[0]
1216+
array([[1, 1, 1],
1217+
[2, 2, 2],
1218+
[3, 3, 3]])
1219+
>>> b.values[1]
1220+
array([[4, 5, 6],
1221+
[4, 5, 6],
1222+
[4, 5, 6]])
1223+
1224+
"""
1225+
if self._values is None:
1226+
self._values = tuple(
1227+
broadcast_to(a, self._shape) for a in self._arrays
1228+
)
1229+
return self._values
1230+
1231+
def __repr__(self):
1232+
return (
1233+
f"<broadcast shape={self.shape}, "
1234+
f"ndim={self.ndim}, size={self.size}>"
1235+
)
1236+
1237+
10501238
def broadcast_arrays(*args, subok=False):
10511239
"""
10521240
Broadcast any number of arrays against each other.
@@ -1055,7 +1243,7 @@ def broadcast_arrays(*args, subok=False):
10551243
10561244
Parameters
10571245
----------
1058-
args : {dpnp.ndarray, usm_ndarray}
1246+
*args : {dpnp.ndarray, usm_ndarray}
10591247
A list of arrays to broadcast.
10601248
10611249
Returns
@@ -1070,6 +1258,9 @@ def broadcast_arrays(*args, subok=False):
10701258
10711259
See Also
10721260
--------
1261+
:obj:`dpnp.broadcast` : Produce an object that mimics broadcasting.
1262+
:obj:`dpnp.broadcast_shapes` : Broadcast the input shapes into a single
1263+
shape.
10731264
:obj:`dpnp.broadcast_to` : Broadcast an array to a new shape.
10741265
10751266
Examples
@@ -1112,6 +1303,7 @@ def broadcast_shapes(*args):
11121303
11131304
See Also
11141305
--------
1306+
:obj:`dpnp.broadcast` : Produce an object that mimics broadcasting.
11151307
:obj:`dpnp.broadcast_arrays` : Broadcast any number of arrays against
11161308
each other.
11171309
:obj:`dpnp.broadcast_to` : Broadcast an array to a new shape.
@@ -1175,8 +1367,11 @@ def broadcast_to(array, /, shape, subok=False):
11751367
11761368
See Also
11771369
--------
1370+
:obj:`dpnp.broadcast` : Produce an object that mimics broadcasting.
11781371
:obj:`dpnp.broadcast_arrays` : Broadcast any number of arrays against
11791372
each other.
1373+
:obj:`dpnp.broadcast_shapes` : Broadcast the input shapes into a single
1374+
shape.
11801375
11811376
Examples
11821377
--------

0 commit comments

Comments
 (0)