From 74490ef5f6ef44c1eb3adf39f90fdf09912fb1e1 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Wed, 4 Oct 2023 09:32:52 -0700 Subject: [PATCH] Inline boolean operators in NumPy are bitwise, not logical (#1057) * Inline boolean operators in NumPy are bitwise, not logical * Add tests for inline operators --- cunumeric/array.py | 28 +++++------ tests/integration/test_binary_ufunc.py | 69 +++++++++++++++++++------- tests/integration/test_unary_ufunc.py | 53 ++++++++++++-------- 3 files changed, 97 insertions(+), 53 deletions(-) diff --git a/cunumeric/array.py b/cunumeric/array.py index 0176798bc..91ad41dde 100644 --- a/cunumeric/array.py +++ b/cunumeric/array.py @@ -817,9 +817,9 @@ def __and__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import logical_and + from ._ufunc import bitwise_and - return logical_and(self, rhs) + return bitwise_and(self, rhs) def __array__( self, dtype: Union[np.dtype[Any], None] = None @@ -1073,9 +1073,9 @@ def __iand__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import logical_and + from ._ufunc import bitwise_and - return logical_and(self, rhs, out=self) + return bitwise_and(self, rhs, out=self) def __idiv__(self, rhs: Any) -> ndarray: """a.__idiv__(value, /) @@ -1186,9 +1186,9 @@ def __ior__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import logical_or + from ._ufunc import bitwise_or - return logical_or(self, rhs, out=self) + return bitwise_or(self, rhs, out=self) def __ipow__(self, rhs: float) -> ndarray: """a.__ipow__(/) @@ -1260,9 +1260,9 @@ def __ixor__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import logical_xor + from ._ufunc import bitwise_xor - return logical_xor(self, rhs, out=self) + return bitwise_xor(self, rhs, out=self) def __le__(self, rhs: Any) -> ndarray: """a.__le__(value, /) @@ -1416,9 +1416,9 @@ def __or__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import logical_or + from ._ufunc import bitwise_or - return logical_or(self, rhs) + return bitwise_or(self, rhs) def __pos__(self) -> ndarray: """a.__pos__(value, /) @@ -1473,9 +1473,9 @@ def __rand__(self, lhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import logical_and + from ._ufunc import bitwise_and - return logical_and(lhs, self) + return bitwise_and(lhs, self) def __rdiv__(self, lhs: Any) -> ndarray: """a.__rdiv__(value, /) @@ -1584,9 +1584,9 @@ def __ror__(self, lhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import logical_or + from ._ufunc import bitwise_or - return logical_or(lhs, self) + return bitwise_or(lhs, self) def __rpow__(self, lhs: Any) -> ndarray: """__rpow__(value, /) diff --git a/tests/integration/test_binary_ufunc.py b/tests/integration/test_binary_ufunc.py index 4d2a9b7db..a6acef049 100644 --- a/tests/integration/test_binary_ufunc.py +++ b/tests/integration/test_binary_ufunc.py @@ -41,32 +41,53 @@ def check_result(op, in_np, out_np, out_num): def check_ops(ops, in_np, out_dtype="D"): + in_num = tuple(num.array(arr) for arr in in_np) + for op in ops: - op_np = getattr(np, op) - op_num = getattr(num, op) + if op.isidentifier(): + op_np = getattr(np, op) + op_num = getattr(num, op) + assert op_np.nout == 1 + + out_np = op_np(*in_np) + out_num = op_num(*in_num) + + check_result(op, in_np, out_np, out_num) - assert op_np.nout == 1 + out_np = np.empty(out_np.shape, dtype=out_dtype) + out_num = num.empty(out_num.shape, dtype=out_dtype) + op_np(*in_np, out=out_np) + op_num(*in_num, out=out_num) - in_num = tuple(num.array(arr) for arr in in_np) + check_result(op, in_np, out_np, out_num) - out_np = op_np(*in_np) - out_num = op_num(*in_num) + # Ask cuNumeric to produce outputs to NumPy ndarrays + out_num = np.empty(out_np.shape, dtype=out_dtype) + op_num(*in_num, out=out_num) - check_result(op, in_np, out_np, out_num) + check_result(op, in_np, out_np, out_num) + + else: + # Doing it this way instead of invoking the dunders directly, to + # avoid having to select the right version, __add__ vs __radd__, + # when one isn't supported, e.g. for scalar.__add__(array) - out_np = np.empty(out_np.shape, dtype=out_dtype) - out_num = num.empty(out_num.shape, dtype=out_dtype) + out_np = eval(f"in_np[0] {op} in_np[1]") + out_num = eval(f"in_num[0] {op} in_num[1]") - op_np(*in_np, out=out_np) - op_num(*in_num, out=out_num) + check_result(op, in_np, out_np, out_num) - check_result(op, in_np, out_np, out_num) + out_np = np.ones_like(out_np) + out_num = num.ones_like(out_num) + exec(f"out_np {op}= in_np[0]") + exec(f"out_num {op}= in_num[0]") - # Ask cuNumeric to produce outputs to NumPy ndarrays - out_num = np.ones(out_np.shape, dtype=out_dtype) - op_num(*in_num, out_num) + check_result(op, in_np, out_np, out_num) - check_result(op, in_np, out_np, out_num) + out_num = np.ones_like(out_np) + exec(f"out_num {op}= in_num[0]") + + check_result(op, in_np, out_np, out_num) def test_all(): @@ -74,8 +95,14 @@ def test_all(): # for some boring inputs. For some of these, we will want to # test corner cases in the future. + # TODO: matmul, @ + # Math operations ops = [ + "*", + "+", + "-", + "/", "add", # "divmod", "equal", @@ -121,6 +148,7 @@ def test_all(): check_ops(ops, (scalar1, scalar2)) ops = [ + "//", "arctan2", "copysign", "floor_divide", @@ -142,6 +170,7 @@ def test_all(): check_ops(ops, (scalar1, scalar2)) ops = [ + "**", "power", "float_power", ] @@ -159,6 +188,7 @@ def test_all(): check_ops(ops, (scalars[3], scalars[0])) ops = [ + "%", "remainder", ] @@ -173,12 +203,17 @@ def test_all(): check_ops(ops, (scalar1, scalar2)) ops = [ + "&", + "<<", + ">>", + "^", + "|", "bitwise_and", "bitwise_or", "bitwise_xor", "gcd", - "left_shift", "lcm", + "left_shift", "right_shift", ] diff --git a/tests/integration/test_unary_ufunc.py b/tests/integration/test_unary_ufunc.py index c1deefe85..9d0021613 100644 --- a/tests/integration/test_unary_ufunc.py +++ b/tests/integration/test_unary_ufunc.py @@ -61,39 +61,46 @@ def check_result(op, in_np, out_np, out_num, **isclose_kwargs): def check_op(op, in_np, out_dtype="d", **check_kwargs): - op_np = getattr(np, op) - op_num = getattr(num, op) + in_num = num.array(in_np) - assert op_np.nout == 1 + if op.isidentifier(): + op_np = getattr(np, op) + op_num = getattr(num, op) - in_num = num.array(in_np) + assert op_np.nout == 1 + + out_np = op_np(in_np) + out_num = op_num(in_num) - out_np = op_np(in_np) - out_num = op_num(in_num) + assert check_result(op, in_np, out_np, out_num, **check_kwargs) - assert check_result(op, in_np, out_np, out_num, **check_kwargs) + out_np = np.empty(out_np.shape, dtype=out_dtype) + out_num = num.empty(out_num.shape, dtype=out_dtype) - out_np = np.empty(out_np.shape, dtype=out_dtype) - out_num = num.empty(out_num.shape, dtype=out_dtype) + op_np(in_np, out=out_np) + op_num(in_num, out=out_num) - op_np(in_np, out=out_np) - op_num(in_num, out=out_num) + assert check_result(op, in_np, out_np, out_num, **check_kwargs) - assert check_result(op, in_np, out_np, out_num, **check_kwargs) + out_np = np.empty(out_np.shape, dtype=out_dtype) + out_num = num.empty(out_num.shape, dtype=out_dtype) - out_np = np.empty(out_np.shape, dtype=out_dtype) - out_num = num.empty(out_num.shape, dtype=out_dtype) + op_np(in_np, out_np) + op_num(in_num, out_num) - op_np(in_np, out_np) - op_num(in_num, out_num) + assert check_result(op, in_np, out_np, out_num, **check_kwargs) - assert check_result(op, in_np, out_np, out_num, **check_kwargs) + # Ask cuNumeric to produce outputs to NumPy ndarrays + out_num = np.ones(out_np.shape, dtype=out_dtype) + op_num(in_num, out_num) - # Ask cuNumeric to produce outputs to NumPy ndarrays - out_num = np.ones(out_np.shape, dtype=out_dtype) - op_num(in_num, out_num) + assert check_result(op, in_np, out_np, out_num, **check_kwargs) + + else: + out_np = eval(f"{op} in_np") + out_num = eval(f"{op} in_num") - assert check_result(op, in_np, out_np, out_num, **check_kwargs) + assert check_result(op, in_np, out_np, out_num, **check_kwargs) def check_ops(ops, in_np, out_dtype="d"): @@ -155,6 +162,8 @@ def check_math_ops(op, **kwargs): # Math operations math_ops = ( + "+", + "-", "absolute", "conjugate", "exp", @@ -283,7 +292,7 @@ def test_arc_hyp_trig_ops(op): check_op(op, np.array(np.random.uniform(low=1, high=5))) -bit_ops = ("invert",) +bit_ops = ("invert", "~") @pytest.mark.parametrize("op", bit_ops)