Skip to content
Merged
1 change: 1 addition & 0 deletions docs/source/reference/numpysupported.rst
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ The following top-level functions are supported:
* :func:`numpy.bincount`
* :func:`numpy.blackman`
* :func:`numpy.broadcast_to` (only the 2 first arguments)
* :func:`numpy.broadcast_arrays` (only the first argument)
* :func:`numpy.column_stack`
* :func:`numpy.concatenate`
* :func:`numpy.convolve` (only the 2 first arguments)
Expand Down
44 changes: 44 additions & 0 deletions numba/np/arrayobj.py
Original file line number Diff line number Diff line change
Expand Up @@ -1382,6 +1382,50 @@ def impl(array, shape):
return impl


@register_jitable
def numpy_broadcast_shapes_list(r, m, shape):
for i in range(len(shape)):
k = m - len(shape) + i
tmp = shape[i]
if tmp == 1:
continue
if r[k] == 1:
r[k] = tmp
elif r[k] != tmp:
raise ValueError("shape mismatch: objects"
" cannot be broadcast"
" to a single shape")


@overload(np.broadcast_arrays)
def numpy_broadcast_arrays(*args):
# number of dimensions
Comment thread
stuartarchibald marked this conversation as resolved.
Resolved
m = max([arr.ndim for arr in args])
tup_init = (0,) * m

def impl(*args):
# find out the output shape
# we can't call np.broadcast_shapes here since args may have arrays
# with different shapes and it is not possible to create a list
# with those shapes dinamically
Comment thread
stuartarchibald marked this conversation as resolved.
Resolved
shape = [1] * m
for array in literal_unroll(args):
numpy_broadcast_shapes_list(shape, m, array.shape)

tup = tup_init

for i in range(m):
tup = tuple_setitem(tup, i, shape[i])

# numpy checks if the input arrays have the same shape as `shape`
outs = []
for array in literal_unroll(args):
outs.append(np.broadcast_to(array, tup))
return outs

return impl


def fancy_setslice(context, builder, sig, args, index_types, indices):
"""
Implement slice assignment for arrays. This implementation works for
Expand Down
127 changes: 127 additions & 0 deletions numba/tests/test_array_manipulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ def numpy_broadcast_to(arr, shape):
return np.broadcast_to(arr, shape)


def numpy_broadcast_arrays(*args):
return np.broadcast_arrays(*args)


def numpy_broadcast_to_indexing(arr, shape, idx):
return np.broadcast_to(arr, shape)[idx]

Expand Down Expand Up @@ -965,6 +969,129 @@ def test_argwhere_array_like(self):
got = cfunc(a)
self.assertPreciseEqual(expected, got)

def broadcast_arrays_assert_correct_shape(self, input_shapes, expected_shape):
# Broadcast a list of arrays with the given input shapes and check the
# common output shape.
pyfunc = numpy_broadcast_arrays
cfunc = jit(nopython=True)(pyfunc)

inarrays = [np.zeros(s) for s in input_shapes]
outarrays = cfunc(*inarrays)
expected = [expected_shape] * len(inarrays)
got = [a.shape for a in outarrays]
self.assertPreciseEqual(expected, got)

def test_broadcast_arrays_same_input_shapes(self):
Comment thread
stuartarchibald marked this conversation as resolved.
Resolved
# Tests taken from
# https://github.com/numpy/numpy/blob/623bc1fae1d47df24e7f1e29321d0c0ba2771ce0/numpy/lib/tests/test_stride_tricks.py#L83-L107 # noqa: E501
# Check that the final shape is just the input shape.
pyfunc = numpy_broadcast_arrays
cfunc = jit(nopython=True)(pyfunc)

data = [
# (),
(1,),
(3,),
(0, 1),
(0, 3),
(1, 0),
(3, 0),
(1, 3),
(3, 1),
(3, 3),
]
for shape in data:
input_shapes = [shape]
# Single input.
self.broadcast_arrays_assert_correct_shape(input_shapes, shape)
# Double input.
input_shapes2 = [shape, shape]
self.broadcast_arrays_assert_correct_shape(input_shapes2, shape)
# Triple input.
input_shapes3 = [shape, shape, shape]
self.broadcast_arrays_assert_correct_shape(input_shapes3, shape)

def test_broadcast_arrays_two_compatible_by_ones_input_shapes(self):
# Tests taken from
# https://github.com/numpy/numpy/blob/623bc1fae1d47df24e7f1e29321d0c0ba2771ce0/numpy/lib/tests/test_stride_tricks.py#L110-L132
# Check that two different input shapes of the same length, but some have
# ones, broadcast to the correct shape.

data = [
[[(1,), (3,)], (3,)],
[[(1, 3), (3, 3)], (3, 3)],
[[(3, 1), (3, 3)], (3, 3)],
[[(1, 3), (3, 1)], (3, 3)],
[[(1, 1), (3, 3)], (3, 3)],
[[(1, 1), (1, 3)], (1, 3)],
[[(1, 1), (3, 1)], (3, 1)],
[[(1, 0), (0, 0)], (0, 0)],
[[(0, 1), (0, 0)], (0, 0)],
[[(1, 0), (0, 1)], (0, 0)],
[[(1, 1), (0, 0)], (0, 0)],
[[(1, 1), (1, 0)], (1, 0)],
[[(1, 1), (0, 1)], (0, 1)],
]
for input_shapes, expected_shape in data:
self.broadcast_arrays_assert_correct_shape(input_shapes, expected_shape)
# Reverse the input shapes since broadcasting should be symmetric.
self.broadcast_arrays_assert_correct_shape(input_shapes[::-1], expected_shape)

def test_broadcast_arrays_two_compatible_by_prepending_ones_input_shapes(self):
# Tests taken from
# https://github.com/numpy/numpy/blob/623bc1fae1d47df24e7f1e29321d0c0ba2771ce0/numpy/lib/tests/test_stride_tricks.py#L135-L164
# Check that two different input shapes (of different lengths) broadcast
# to the correct shape.

data = [
[[(), (3,)], (3,)],
[[(3,), (3, 3)], (3, 3)],
[[(3,), (3, 1)], (3, 3)],
[[(1,), (3, 3)], (3, 3)],
[[(), (3, 3)], (3, 3)],
[[(1, 1), (3,)], (1, 3)],
[[(1,), (3, 1)], (3, 1)],
[[(1,), (1, 3)], (1, 3)],
[[(), (1, 3)], (1, 3)],
[[(), (3, 1)], (3, 1)],
[[(), (0,)], (0,)],
[[(0,), (0, 0)], (0, 0)],
[[(0,), (0, 1)], (0, 0)],
[[(1,), (0, 0)], (0, 0)],
[[(), (0, 0)], (0, 0)],
[[(1, 1), (0,)], (1, 0)],
[[(1,), (0, 1)], (0, 1)],
[[(1,), (1, 0)], (1, 0)],
[[(), (1, 0)], (1, 0)],
[[(), (0, 1)], (0, 1)],
]
for input_shapes, expected_shape in data:
self.broadcast_arrays_assert_correct_shape(input_shapes, expected_shape)
# Reverse the input shapes since broadcasting should be symmetric.
self.broadcast_arrays_assert_correct_shape(input_shapes[::-1], expected_shape)

def test_broadcast_arrays_incompatible_shapes_raise_valueerror(self):
# Check that a ValueError is raised for incompatible shapes.
pyfunc = numpy_broadcast_arrays
cfunc = jit(nopython=True)(pyfunc)

self.disable_leak_check()

data = [
[(3,), (4,)],
[(2, 3), (2,)],
[(3,), (3,), (4,)],
[(1, 3, 4), (2, 3, 3)],
]
for input_shapes in data:
for shape in [input_shapes, input_shapes[::-1]]:
# Reverse the input shapes since broadcasting should be symmetric.
with self.assertRaises(ValueError) as raises:
inarrays = [np.zeros(s) for s in shape]
cfunc(*inarrays)
self.assertIn("shape mismatch: objects cannot be broadcast to a single shape",
str(raises.exception))
Comment thread
guilhermeleobas marked this conversation as resolved.
Resolved


if __name__ == '__main__':
unittest.main()