diff --git a/tests/integration/test_unary_ufunc.py b/tests/integration/test_unary_ufunc.py index 6db6d1bd80..ee0f7f16b3 100644 --- a/tests/integration/test_unary_ufunc.py +++ b/tests/integration/test_unary_ufunc.py @@ -21,33 +21,11 @@ import cunumeric as num +np.random.seed(12345) -def deterministic_op_test(func): - # Uses the op name to create a deterministic seed. - # This enforces that inputs are always the same whether - # running all tests or a single test with -k. - def wrapper_set_seed(op, *args, **kwargs): - # np seeds must be limited to 32-bits - np.random.seed(hash(op) & 0xFFFFFFFF) - func(op, *args, **kwargs) - func(op, *args, **kwargs) - - return wrapper_set_seed - - -def check_result(op, in_np, out_np, out_num, **isclose_kwargs): - if in_np.dtype == "e" or out_np.dtype == "e": - # The mantissa is only 10 bits, 2**-10 ~= 10^(-4) - # Gives 1e-3 as rtol to provide extra rounding error. - f16_rtol = 1e-3 - rtol = isclose_kwargs.setdefault("rtol", f16_rtol) - # make sure we aren't trying to fp16 compare with less precision - assert rtol >= f16_rtol - - result = ( - allclose(out_np, out_num, **isclose_kwargs) - and out_np.dtype == out_num.dtype - ) + +def check_result(op, in_np, out_np, out_num): + result = allclose(out_np, out_num) and out_np.dtype == out_num.dtype if not result: print(f"cunumeric.{op} failed the test") print("Inputs:") @@ -63,40 +41,40 @@ def check_result(op, in_np, out_np, out_num, **isclose_kwargs): return result -def check_op(op, in_np, out_dtype="d", **check_kwargs): +def check_op(op, in_np, out_dtype="d"): op_np = getattr(np, op) op_num = getattr(num, op) assert op_np.nout == 1 - in_num = num.array(in_np) + in_num = tuple(num.array(arr) for arr in in_np) - 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) 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) 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) # Ask cuNumeric to produce outputs to NumPy ndarrays out_num = np.ones(out_np.shape, dtype=out_dtype) - op_num(in_num, out_num) + 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) def check_ops(ops, in_np, out_dtype="d"): @@ -104,58 +82,11 @@ def check_ops(ops, in_np, out_dtype="d"): check_op(op, in_np, out_dtype) -def check_op_input( - op, - shape=(4, 5), - a_min=None, - a_max=None, - randint=False, - offset=None, - astype=None, - out_dtype="d", - replace_zero=None, - **check_kwargs, -): - if randint: - assert a_min is not None - assert a_max is not None - in_np = np.random.randint(a_min, a_max, size=shape) - else: - in_np = np.random.randn(*shape) - if offset is not None: - in_np = in_np + offset - if a_min is not None: - in_np = np.maximum(a_min, in_np) - if a_max is not None: - in_np = np.minimum(a_max, in_np) - if astype is not None: - in_np = in_np.astype(astype) - - if replace_zero is not None: - in_np[in_np == 0] = replace_zero - - # converts to a scalar if shape is (1,) - if in_np.ndim == 1 and in_np.shape[0] == 1: - in_np = in_np[0] - check_op(op, in_np, out_dtype=out_dtype, **check_kwargs) - - # TODO: right now we will simply check if the operations work # for some boring inputs. For some of these, we will want to # test corner cases in the future. -@deterministic_op_test -def check_math_ops(op, **kwargs): - check_op_input(op, **kwargs) - check_op_input(op, astype="e", **kwargs) - check_op_input(op, astype="f", **kwargs) - check_op_input(op, astype="b", **kwargs) - check_op_input(op, astype="B", **kwargs) - check_op_input(op, randint=True, a_min=1, a_max=10, **kwargs) - check_op_input(op, shape=(1,), **kwargs) - - # Math operations math_ops = ( "absolute", @@ -167,6 +98,7 @@ def check_math_ops(op, **kwargs): "logical_not", "negative", "positive", + "reciprocal", "rint", "sign", "square", @@ -174,19 +106,14 @@ def check_math_ops(op, **kwargs): @pytest.mark.parametrize("op", math_ops) -def test_default_math_ops(op): - check_math_ops(op) - - -special_math_ops = ( - # reciprocal is undefined on zero, replaces with 1 - ("reciprocal", dict(replace_zero=1)), -) - - -@pytest.mark.parametrize("op,kwargs", special_math_ops) -def test_special_math_ops(op, kwargs): - check_math_ops(op, **kwargs) +def test_math_ops(op): + check_op(op, (np.random.randn(4, 5),)) + check_op(op, (np.random.randn(4, 5).astype("e"),)) + check_op(op, (np.random.randn(4, 5).astype("f"),)) + check_op(op, (np.random.randn(4, 5).astype("b"),)) + check_op(op, (np.random.randn(4, 5).astype("B"),)) + check_op(op, (np.random.randint(1, 10, size=(4, 5)),)) + check_op(op, (np.random.randn(1)[0],)) log_ops = ( @@ -198,54 +125,38 @@ def test_special_math_ops(op, kwargs): @pytest.mark.parametrize("op", log_ops) -@deterministic_op_test -def test_log_ops(op): - # for real-valued log functions, requires inputs to be positive - # since numpy does log(real) -> real and not log(real)->complex - # for negative inputs - check_op_input(op, offset=3, a_min=0.1) - check_op_input(op, astype="e", offset=3, a_min=0.1) - check_op_input(op, astype="f", offset=3, a_min=0.1) - - # for real-valued log functions, allows negative values and checks - # that nans are returned appropriately for bad cases - check_op_input(op, equal_nan=True) - - # for the complex case, this allows negative input values - # in order to produce complex output values - check_op_input(op, astype="F", out_dtype="D") - - check_op_input(op, randint=True, a_min=3, a_max=10) - check_op_input(op, shape=(1,), offset=3) +def test_power_ops(op): + check_op(op, (np.random.randn(4, 5) + 3,)) + check_op(op, (np.random.randn(4, 5).astype("e") + 3,)) + check_op(op, (np.random.randn(4, 5).astype("f") + 3,)) + check_op(op, (np.random.randn(4, 5).astype("F") + 3,), out_dtype="D") + check_op(op, (np.random.randint(3, 10, size=(4, 5)),)) + check_op(op, (np.random.randn(1)[0] + 3,)) even_root_ops = ("sqrt",) @pytest.mark.parametrize("op", even_root_ops) -@deterministic_op_test def test_even_root_ops(op): - # Need to guarantee positive inputs with a_min # for float roots - check_op_input(op, offset=3, a_min=0) - check_op_input(op, astype="e", offset=3, a_min=0) - check_op_input(op, astype="f", offset=3, a_min=0) - # Complex inputs can be negative - check_op_input(op, astype="F", out_dtype="D") - check_op_input(op, randint=True, a_min=3, a_max=10) - check_op_input(op, shape=(1,), offset=3) + check_op(op, (np.random.randn(4, 5) + 3,)) + check_op(op, (np.random.randn(4, 5).astype("e") + 3,)) + check_op(op, (np.random.randn(4, 5).astype("f") + 3,)) + check_op(op, (np.random.randn(4, 5).astype("F") + 3,), out_dtype="D") + check_op(op, (np.random.randint(3, 10, size=(4, 5)),)) + check_op(op, (np.random.randn(1)[0] + 3,)) odd_root_ops = ("cbrt",) @pytest.mark.parametrize("op", odd_root_ops) -@deterministic_op_test def test_odd_root_ops(op): - check_op(op, np.random.randn(4, 5)) - check_op(op, np.random.randn(4, 5).astype("e")) - check_op(op, np.random.randn(4, 5).astype("f")) - check_op(op, np.random.randint(0, 10, size=(4, 5))) - check_op(op, np.random.randn(1)[0] + 3) + check_op(op, (np.random.randn(4, 5),)) + check_op(op, (np.random.randn(4, 5).astype("e"),)) + check_op(op, (np.random.randn(4, 5).astype("f"),)) + check_op(op, (np.random.randint(0, 10, size=(4, 5)),)) + check_op(op, (np.random.randn(1)[0] + 3,)) trig_ops = ( @@ -265,11 +176,10 @@ def test_odd_root_ops(op): @pytest.mark.parametrize("op", trig_ops) -@deterministic_op_test def test_trig_ops(op): - check_op(op, np.random.uniform(low=-1, high=1, size=(4, 5))) - check_op(op, np.random.uniform(low=-1, high=1, size=(4, 5)).astype("e")) - check_op(op, np.array(np.random.uniform(low=-1, high=1))) + check_op(op, (np.random.uniform(low=-1, high=1, size=(4, 5)),)) + check_op(op, (np.random.uniform(low=-1, high=1, size=(4, 5)).astype("e"),)) + check_op(op, (np.array(np.random.uniform(low=-1, high=1)),)) arc_hyp_trig_ops = ( @@ -279,21 +189,19 @@ def test_trig_ops(op): @pytest.mark.parametrize("op", arc_hyp_trig_ops) -@deterministic_op_test def test_arc_hyp_trig_ops(op): - check_op(op, np.random.uniform(low=1, high=5, size=(4, 5))) - check_op(op, np.random.uniform(low=1, high=5, size=(4, 5)).astype("e")) - check_op(op, np.array(np.random.uniform(low=1, high=5))) + check_op(op, (np.random.uniform(low=1, high=5, size=(4, 5)),)) + check_op(op, (np.random.uniform(low=1, high=5, size=(4, 5)).astype("e"),)) + check_op(op, (np.array(np.random.uniform(low=1, high=5)),)) bit_ops = ("invert",) @pytest.mark.parametrize("op", bit_ops) -@deterministic_op_test def test_bit_ops(op): - check_op(op, np.random.randint(0, 2, size=(4, 5))) - check_op(op, np.random.randint(0, 1, size=(4, 5), dtype="?")) + check_op(op, (np.random.randint(0, 2, size=(4, 5)),)) + check_op(op, (np.random.randint(0, 1, size=(4, 5), dtype="?"),)) comparison_ops = ("logical_not",) @@ -301,7 +209,7 @@ def test_bit_ops(op): @pytest.mark.parametrize("op", comparison_ops) def test_comparison_ops(op): - check_op(op, np.random.randint(0, 2, size=(4, 5))) + check_op(op, (np.random.randint(0, 2, size=(4, 5)),)) floating_ops = ( @@ -314,14 +222,13 @@ def test_comparison_ops(op): @pytest.mark.parametrize("op", floating_ops) -@deterministic_op_test def test_floating_ops(op): - check_op(op, np.random.randn(4, 5)) - check_op(op, np.random.randn(4, 5).astype("f")) - check_op(op, np.random.randn(4, 5).astype("e")) - check_op(op, np.random.randint(0, 10, size=(4, 5))) - check_op(op, np.random.randint(0, 10, size=(4, 5), dtype="I")) - check_op(op, np.random.randn(1)[0] + 3) + check_op(op, (np.random.randn(4, 5),)) + check_op(op, (np.random.randn(4, 5).astype("f"),)) + check_op(op, (np.random.randn(4, 5).astype("e"),)) + check_op(op, (np.random.randint(0, 10, size=(4, 5)),)) + check_op(op, (np.random.randint(0, 10, size=(4, 5), dtype="I"),)) + check_op(op, (np.random.randn(1)[0] + 3,)) nan_ops = ( @@ -334,10 +241,10 @@ def test_floating_ops(op): @pytest.mark.parametrize("op", nan_ops) def test_nan_ops(op): - check_op(op, np.array([-np.inf, 0.0, 1.0, np.inf, np.nan])) - check_op(op, np.array([-np.inf, 0.0, 1.0, np.inf, np.nan], dtype="F")) - check_op(op, np.array([-np.inf, 0.0, 1.0, np.inf, np.nan], dtype="e")) - check_op(op, np.array(np.inf)) + check_op(op, (np.array([-np.inf, 0.0, 1.0, np.inf, np.nan]),)) + check_op(op, (np.array([-np.inf, 0.0, 1.0, np.inf, np.nan], dtype="F"),)) + check_op(op, (np.array([-np.inf, 0.0, 1.0, np.inf, np.nan], dtype="e"),)) + check_op(op, (np.array(np.inf),)) def parse_inputs(in_str, dtype_str):