v923z / micropython-ulab

a numpy-like fast vector module for micropython, circuitpython, and their derivatives
https://micropython-ulab.readthedocs.io/en/latest
MIT License
430 stars 117 forks source link

[BUG] - Possible broadcast issue with empty arrays #506

Closed wired8 closed 2 years ago

wired8 commented 2 years ago

Describe the bug In cpython it seems that some binary operations involving an empty numpy array return an empty numpy array.

This difference in behavior is a problem when porting code.

To Reproduce

print(np.array([])+np.array([1.]))
>> array([1.0], dtype=float64)

Expected behavior

print(np.array([])+np.array([1.]))
>> array([], dtype=float64)

Notes: https://stackoverflow.com/questions/58854669/python-list-empty-numpy-array-empty-numpy-array

v923z commented 2 years ago

Describe the bug In cpython it seems that some binary operations involving an empty numpy array return an empty numpy array.

It is some, or all? The empty array is not a very well documented feature of numpy, and it's tripped up in the past.

wired8 commented 2 years ago

Looking like some, sorry should have been more specific.

print(np.array([])+np.array([1.]))
>>> array([1.0], dtype=float64)

print(np.array([])-np.array([1.]))
>>> array([-1.0], dtype=float64)

print(np.array([])/np.array([1.]))
>>> array([0.0], dtype=float64)

print(np.array([])*np.array([1.]))
>>> array([0.0], dtype=float64)
a = np.array([])
a *= 1
print(a)
>>> array([], dtype=float64)

b = np.array([])
b /= 1
print(b)
>>> array([], dtype=float64)

c = np.array([])
c += 1
print(c)
>>> array([], dtype=float64)

d = np.array([])
d -= 1
print(d)
>>> array([], dtype=float64)
v923z commented 2 years ago

If I run this with CPython

try:
    from ulab import numpy as np
except:
    import numpy as np

print(np.array([])+np.array([1.]))

print(np.array([])-np.array([1.]))

print(np.array([])/np.array([1.]))

print(np.array([])*np.array([1.]))

a = np.array([])
a *= 1
print(a)

b = np.array([])
b /= 1
print(b)

c = np.array([])
c += 1
print(c)

d = np.array([])
d -= 1
print(d)

I get this:

[]
[]
[]
[]
[]
[]
[]
[]

On the other hand, in ulab, the output is

array([1.0], dtype=float64)
array([-1.0], dtype=float64)
array([0.0], dtype=float64)
array([0.0], dtype=float64)
array([], dtype=float64)
array([], dtype=float64)
array([], dtype=float64)
array([], dtype=float64)

So, I believe, all operators result in an empty array, if one of the operands is empty. Perhaps, the easiest fix is to check, whether the array is empty at the very beginning, and return immediately, if so.

wired8 commented 2 years ago

That has been my current approach, but given my use case and the share amount of code I have I'm concerned with having to add this check. What is the overhead for checking this c side?

v923z commented 2 years ago

That has been my current approach, but given my use case and the share amount of code I have I'm concerned with having to add this check. What is the overhead for checking this c side?

Oh, I meant that this should obviously be done in firmware, don't worry! My comment was sort of a question as to whether this would be enough to ensure compatibility.

wired8 commented 2 years ago

Thanks @v923z your a champion!

v923z commented 2 years ago

I think, https://github.com/v923z/micropython-ulab/pull/507 should rectify the problem. As soon as you can confirm it, I would merge this.