Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This release is compatible with NumPy 2.5.
* Added implementation of `dpnp.lib.stride_tricks.as_strided` [#2991](https://github.com/IntelPython/dpnp/pull/2991)
* 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)
* Added support for free-threaded Python builds [gh-3026](https://github.com/IntelPython/dpnp/pull/3026)
* Added `dpnp.broadcast` class implementation [#2901](https://github.com/IntelPython/dpnp/pull/2901)

### Changed

Expand Down
28 changes: 28 additions & 0 deletions doc/_templates/autosummary/class_with_attributes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{% extends "!autosummary/class.rst" %}

{% block methods %}
{% if methods %}
.. HACK -- the point here is that we don't want this to appear in the output, but the autosummary should still generate the pages.
.. autosummary::
:toctree:
{% for item in all_methods %}
{%- if not item.startswith('_') or item in ['__call__'] %}
{{ name }}.{{ item }}
{%- endif -%}
{%- endfor %}
{% endif %}
{% endblock %}

{% block attributes %}
{% if attributes %}
.. rubric:: {{ _('Attributes') }}

.. autosummary::
:toctree:
{% for item in all_attributes %}
{%- if not item.startswith('_') %}
~{{ name }}.{{ item }}
{%- endif -%}
{%- endfor %}
{% endif %}
{% endblock %}
1 change: 1 addition & 0 deletions doc/known_words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Nj
Nk
normed
nuc
numiter
numpy
nx
ny
Expand Down
8 changes: 7 additions & 1 deletion doc/reference/array-manipulation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,20 @@ Transpose-like operations
Changing number of dimensions
-----------------------------

.. autosummary::
:toctree: generated/
:nosignatures:
:template: autosummary/class_with_attributes.rst

broadcast

.. autosummary::
:toctree: generated/
:nosignatures:

atleast_1d
atleast_2d
atleast_3d
broadcast
broadcast_to
broadcast_arrays
expand_dims
Expand Down
2 changes: 2 additions & 0 deletions dpnp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@
atleast_1d,
atleast_2d,
atleast_3d,
broadcast,
broadcast_arrays,
broadcast_to,
column_stack,
Expand Down Expand Up @@ -691,6 +692,7 @@
"atleast_1d",
"atleast_2d",
"atleast_3d",
"broadcast",
"broadcast_arrays",
"broadcast_to",
"column_stack",
Expand Down
197 changes: 196 additions & 1 deletion dpnp/dpnp_iface_manipulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
from .dpnp_utils import get_usm_allocations
from .dpnp_utils.dpnp_utils_pad import dpnp_pad
from .exceptions import AxisError
from .tensor._manipulation_functions import _broadcast_shapes
from .tensor._numpy_helper import (
normalize_axis_index,
normalize_axis_tuple,
Expand Down Expand Up @@ -1047,6 +1048,193 @@ def atleast_3d(*arys):
return tuple(res)


class broadcast: # pylint: disable=invalid-name
"""
Produce an object that mimics broadcasting.

For full documentation refer to :obj:`numpy.broadcast`.

Parameters
----------
*args : {dpnp.ndarray, usm_ndarray}
Input arrays to broadcast against one another.

Returns
-------
broadcast : broadcast object
Broadcast the input parameters against one another, and
return an object that encapsulates the result.
Amongst others, it has ``shape`` and ``ndim`` properties.

Limitations
-----------
Input arrays are not coerced, so array-like objects and scalars are not
supported and ``TypeError`` exception will be raised.

See Also
--------
:obj:`dpnp.broadcast_arrays` : Broadcast any number of arrays against
each other.
:obj:`dpnp.broadcast_shapes` : Broadcast the input shapes into a single
shape.
:obj:`dpnp.broadcast_to` : Broadcast an array to a new shape.

Notes
-----
Iterator functionality is not supported.

The legacy ``nd`` attribute of :obj:`numpy.broadcast` is not provided,
``ndim`` has to be used instead.

Examples
--------
>>> import dpnp as np
>>> x = np.array([[1], [2], [3]])
>>> y = np.array([4, 5, 6])
>>> b = np.broadcast(x, y)
>>> b.shape
(3, 3)
>>> b.ndim
2
>>> b.size
9

"""

def __init__(self, *args):
dpnp.check_supported_arrays_type(*args)

self._arrays = args
self._values = None

# _broadcast_shapes() does not accept an empty sequence of arrays
self._shape = _broadcast_shapes(*args) if args else ()
self._size = math.prod(self._shape)
self._ndim = len(self._shape)

@property
def shape(self):
"""
Shape of the broadcasted result.

Returns
-------
out : tuple
A tuple containing the shape of the broadcasted result.

Examples
--------
>>> import dpnp as np
>>> x = np.array([[1], [2], [3]])
>>> y = np.array([4, 5, 6])
>>> np.broadcast(x, y).shape
(3, 3)

"""
return self._shape

@property
def size(self):
"""
Total size of the broadcasted result.

Returns
-------
out : int
The total size (number of elements) of the broadcasted result.

Examples
--------
>>> import dpnp as np
>>> x = np.array([[1], [2], [3]])
>>> y = np.array([4, 5, 6])
>>> np.broadcast(x, y).size
9

"""
return self._size

@property
def ndim(self):
"""
Number of dimensions of the broadcasted result.

Returns
-------
out : int
The number of dimensions of the broadcasted result.

Examples
--------
>>> import dpnp as np
>>> x = np.array([[1], [2], [3]])
>>> y = np.array([4, 5, 6])
>>> np.broadcast(x, y).ndim
2

"""
return self._ndim

@property
def numiter(self):
"""
Number of iterators possessed by the broadcast object.

Returns
-------
out : int
The number of iterators.

Examples
--------
>>> import dpnp as np
>>> x = np.array([[1], [2], [3]])
>>> y = np.array([4, 5, 6])
>>> np.broadcast(x, y).numiter
2

"""
return len(self._arrays)

@property
def values(self):
"""
The input arrays broadcast against one another.

Returns
-------
out : tuple of dpnp.ndarray
A tuple of arrays which are views on the original input arrays.

Examples
--------
>>> import dpnp as np
>>> x = np.array([[1], [2], [3]])
>>> y = np.array([4, 5, 6])
>>> b = np.broadcast(x, y)
>>> b.values[0]
array([[1, 1, 1],
[2, 2, 2],
[3, 3, 3]])
>>> b.values[1]
array([[4, 5, 6],
[4, 5, 6],
[4, 5, 6]])

"""
if self._values is None:
self._values = tuple(
broadcast_to(a, self._shape) for a in self._arrays
)
return self._values

def __repr__(self):
return (
f"<broadcast shape={self.shape}, "
f"ndim={self.ndim}, size={self.size}>"
)


def broadcast_arrays(*args, subok=False):
"""
Broadcast any number of arrays against each other.
Expand All @@ -1055,7 +1243,7 @@ def broadcast_arrays(*args, subok=False):

Parameters
----------
args : {dpnp.ndarray, usm_ndarray}
*args : {dpnp.ndarray, usm_ndarray}
A list of arrays to broadcast.

Returns
Expand All @@ -1070,6 +1258,9 @@ def broadcast_arrays(*args, subok=False):

See Also
--------
:obj:`dpnp.broadcast` : Produce an object that mimics broadcasting.
:obj:`dpnp.broadcast_shapes` : Broadcast the input shapes into a single
shape.
:obj:`dpnp.broadcast_to` : Broadcast an array to a new shape.

Examples
Expand Down Expand Up @@ -1112,6 +1303,7 @@ def broadcast_shapes(*args):

See Also
--------
:obj:`dpnp.broadcast` : Produce an object that mimics broadcasting.
:obj:`dpnp.broadcast_arrays` : Broadcast any number of arrays against
each other.
:obj:`dpnp.broadcast_to` : Broadcast an array to a new shape.
Expand Down Expand Up @@ -1175,8 +1367,11 @@ def broadcast_to(array, /, shape, subok=False):

See Also
--------
:obj:`dpnp.broadcast` : Produce an object that mimics broadcasting.
:obj:`dpnp.broadcast_arrays` : Broadcast any number of arrays against
each other.
:obj:`dpnp.broadcast_shapes` : Broadcast the input shapes into a single
shape.

Examples
--------
Expand Down
Loading
Loading