Skip to content

test_iop on arithmetic operators (test_special_cases.py) #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
simonetgordon opened this issue Jun 24, 2022 · 11 comments
Closed

test_iop on arithmetic operators (test_special_cases.py) #130

simonetgordon opened this issue Jun 24, 2022 · 11 comments
Labels

Comments

@simonetgordon
Copy link
Contributor

For numpy, I only have test_iop failures for arithmetic operator methods (imul, iadd, ifloordiv, etc.).

Take ifloordiv, for example. I obtain the following test error message:

E               AssertionError: out=-1.1754943508222875e-38, but should be +0 [__ifloordiv__()]
E                 condition: isfinite(x1_i) and x1_i < 0 and x2_i is -infinity -> +0
E                 x1=-1.1754943508222875e-38, x2=-inf

However, when I run the corresponding case on a script, I obtain the expected result:

x1=-1.1754943508222875e-38
x2=-float('inf')
x1 //= x2
print(x1) # 0.0

Any insight would be much appreciated 😊

@asmeurer
Copy link
Member

Your test script is using Python floats. The test will be using arrays (probably 0-D arrays). I'm guessing your array library prints 0-D arrays as if they were Python scalars. The error message is using the repr of the array.

@simonetgordon
Copy link
Contributor Author

Thanks for getting back to me. I seem to obtain the correct result when I use 0-dim arrays too:

x1=ivy.array(-1.1754943508222875e-38)
x2=ivy.array(-float('inf'))
x1 //= x2
print(x1) # ivy.array(0.)

I also put some prints in test_iop and tried to replicate the exact case again in the python script:

----------------------------- Captured stdout call -----------------------------
---------------------
iop_name = __ifloordiv__, iop = <built-in function ifloordiv>, case = x1_i is +infinity and isfinite(x2_i) and x2_i < 0 -> -infinity
---------------------
x1: ivy.array(inf) (float32), x2 = ivy.array(-1.1754944e-38) (float32)
res = ivy.array(inf)
iop(res,x2) = ivy.array(-inf)
l = inf, r = -1.1754943508222875e-38, o = inf

Corresponding python script:

from math import inf
x1=ivy.array(inf, dtype='float32')
x2=ivy.array(-1.1754944e-38, dtype='float32')
x1 //= x2
print(x1) # ivy.array(-inf)

It seems the o variable is taking the res array value. Is res supposed to be the resultant x1 value? As it appears to be just be the input x1 value. Apologies if this is what you implied by " The error message is using the repr of the array.", I'm just not quite sure how to go about fixing this as all the prints look good up to that point.

@simonetgordon
Copy link
Contributor Author

When I set this line to res, I get all tests passing.

@honno
Copy link
Member

honno commented Jun 27, 2022

Is res supposed to be the resultant x1 value?

Yep, res is the result of an in-place operation. It first is a copy of x1, and then is applied the in-place operation (via operator in Python's stdlib) with x2. We copy x1 so that we can use the original x1 after the in-place operation.

res = xp.asarray(x1, copy=True)
iop(res, x2)

Maybe ivy doesn't support copy=True properly for it's xp.asarray() implementation?

It's worth noting NumPy doesn't support a few special cases, namely the floordiv family of special cases. We list them in our GitHub Actions workflow

# https://github.com/numpy/numpy/issues/21211
array_api_tests/test_special_cases.py::test_iop[__iadd__(x1_i is -0 and x2_i is -0) -> -0]
# https://github.com/numpy/numpy/issues/21213
array_api_tests/test_special_cases.py::test_iop[__ipow__(x1_i is -infinity and x2_i > 0 and not (x2_i.is_integer() and x2_i % 2 == 1)) -> +infinity]
array_api_tests/test_special_cases.py::test_iop[__ipow__(x1_i is -0 and x2_i > 0 and not (x2_i.is_integer() and x2_i % 2 == 1)) -> +0]
# noted diversions from spec
array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i > 0) -> +infinity]
array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i < 0) -> -infinity]
array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is -infinity and isfinite(x2_i) and x2_i > 0) -> -infinity]
array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is -infinity and isfinite(x2_i) and x2_i < 0) -> +infinity]
array_api_tests/test_special_cases.py::test_binary[floor_divide(isfinite(x1_i) and x1_i > 0 and x2_i is -infinity) -> -0]
array_api_tests/test_special_cases.py::test_binary[floor_divide(isfinite(x1_i) and x1_i < 0 and x2_i is +infinity) -> -0]
array_api_tests/test_special_cases.py::test_binary[__floordiv__(x1_i is +infinity and isfinite(x2_i) and x2_i > 0) -> +infinity]
array_api_tests/test_special_cases.py::test_binary[__floordiv__(x1_i is +infinity and isfinite(x2_i) and x2_i < 0) -> -infinity]
array_api_tests/test_special_cases.py::test_binary[__floordiv__(x1_i is -infinity and isfinite(x2_i) and x2_i > 0) -> -infinity]
array_api_tests/test_special_cases.py::test_binary[__floordiv__(x1_i is -infinity and isfinite(x2_i) and x2_i < 0) -> +infinity]
array_api_tests/test_special_cases.py::test_binary[__floordiv__(isfinite(x1_i) and x1_i > 0 and x2_i is -infinity) -> -0]
array_api_tests/test_special_cases.py::test_binary[__floordiv__(isfinite(x1_i) and x1_i < 0 and x2_i is +infinity) -> -0]
array_api_tests/test_special_cases.py::test_iop[__ifloordiv__(x1_i is +infinity and isfinite(x2_i) and x2_i > 0) -> +infinity]
array_api_tests/test_special_cases.py::test_iop[__ifloordiv__(x1_i is +infinity and isfinite(x2_i) and x2_i < 0) -> -infinity]
array_api_tests/test_special_cases.py::test_iop[__ifloordiv__(x1_i is -infinity and isfinite(x2_i) and x2_i > 0) -> -infinity]
array_api_tests/test_special_cases.py::test_iop[__ifloordiv__(x1_i is -infinity and isfinite(x2_i) and x2_i < 0) -> +infinity]
array_api_tests/test_special_cases.py::test_iop[__ifloordiv__(isfinite(x1_i) and x1_i > 0 and x2_i is -infinity) -> -0]
array_api_tests/test_special_cases.py::test_iop[__ifloordiv__(isfinite(x1_i) and x1_i < 0 and x2_i is +infinity) -> -0]

but yeah you've got some errors outside of the known ones. It's worth noting we don't reproduce them.

@simonetgordon
Copy link
Contributor Author

OK I'll check out the ivy xp.asarray() implementation, thanks! As for the special cases, yes I am aware, I have gotten floor_divide passing by using floor(divide(x1, x2) for these special cases in the backend. The only outstanding failures for numpy at the moment are pertaining to test_iop.

@simonetgordon
Copy link
Contributor Author

I tried to replicate the way asarray is being used in this test_iop in a python script using a //= operator, with inputs used in the first example in this thread. Can you confirm that this is the expected behaviour?

import ivy
ivy.set_backend('numpy')

x1=ivy.array(-1.1754943508222875e-38)
x2=ivy.array(-float('inf'))
res = ivy.asarray(x1, copy=True)
res //= x2
print("res = {}, x1 = {}".format(res,x1)) # res = ivy.array(0.), x1 = ivy.array(-1.1754944e-38)

When I used operator.__ifloor__ instead of //=, it does not seem to update res to being the resultant x1:

import operator

x1=ivy.array(-1.1754943508222875e-38)
x2=ivy.array(-float('inf'))
res = ivy.asarray(x1, copy=True)
operator.__ifloordiv__(res, x2)
print("res = {}, x1 = {}".format(res,x1)) # res = ivy.array(-1.1754944e-38), x1 = ivy.array(-1.1754944e-38)

Let me know if I have mis-implemented the in-place operation, I have never used this before.

@honno
Copy link
Member

honno commented Jun 27, 2022

Can you confirm that this is the expected behaviour?

import ivy
ivy.set_backend('numpy')

x1=ivy.array(-1.1754943508222875e-38)
x2=ivy.array(-float('inf'))
res = ivy.asarray(x1, copy=True)
res //= x2
print("res = {}, x1 = {}".format(res,x1)) # res = ivy.array(0.), x1 = ivy.array(-1.1754944e-38)

Yep that's as expected.

When I used operator.__ifloor__ instead of //=, it does not seem to update res to being the resultant x1:

import operator

x1=ivy.array(-1.1754943508222875e-38)
x2=ivy.array(-float('inf'))
res = ivy.asarray(x1, copy=True)
operator.__ifloordiv__(res, x2)
print("res = {}, x1 = {}".format(res,x1)) # res = ivy.array(-1.1754944e-38), x1 = ivy.array(-1.1754944e-38)

Interesting! I'm not quite sure why these would be different, as operator.__ifloordiv__(res, x2) should just mirror the behaviour of res //= x2. I can explore this... I should go through ivy issues all at once heh. Low prio for now.

@honno honno added the low priority Low priority issue label Jun 27, 2022
@simonetgordon
Copy link
Contributor Author

simonetgordon commented Jun 27, 2022

On the operator page you linked me, this is what they have for the operator.__ifloordiv__ implementation:

operator.ifloordiv(a, b)
operator.__ifloordiv__(a, b)
a = ifloordiv(a, b) is equivalent to a //= b.

it seems it might be necessary to set res = iop(res, x2) ?

@honno
Copy link
Member

honno commented Jun 27, 2022

Ah forgot it returns. So interestingly the in-place operators do return results, but for any "mutable" target it should execute the in-place operator on the target (see In-place Operators). I wonder how mutability is exactly inferred here, as that could be what trips up running ivy. In any case, yes assigning the result of the iop does look necessary.

@simonetgordon
Copy link
Contributor Author

That's interesting. So the crucial line here is:
For immutable targets such as strings, numbers, and tuples, the updated value is computed, but not assigned back to the input variable

@simonetgordon
Copy link
Contributor Author

Shall I make a PR by the way?

asmeurer added a commit that referenced this issue Jun 27, 2022
test_iop fix: assign `res` to in-place operator function #130
@honno honno closed this as completed Jul 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants