diff --git a/arrayfire_wrapper/lib/mathematical_functions/numeric_functions.py b/arrayfire_wrapper/lib/mathematical_functions/numeric_functions.py index df37881..7cbb00c 100644 --- a/arrayfire_wrapper/lib/mathematical_functions/numeric_functions.py +++ b/arrayfire_wrapper/lib/mathematical_functions/numeric_functions.py @@ -7,6 +7,10 @@ from arrayfire_wrapper.lib.mathematical_functions.arithmetic_operations import sub +import arrayfire_wrapper.dtypes as dtype +import arrayfire_wrapper.lib as wrapper + + def abs_(arr: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__abs.htm#ga7e8b3c848e6cda3d1f3b0c8b2b4c3f8f @@ -44,12 +48,12 @@ def floor(arr: AFArray, /) -> AFArray: return unary_op(floor.__name__, arr) -def hypot(lhs: AFArray, rhs: AFArray, /) -> AFArray: +def hypot(lhs: AFArray, rhs: AFArray, batch: bool, /) -> AFArray: """ source: """ out = AFArray.create_null_pointer() - call_from_clib(hypot.__name__, lhs, rhs) + call_from_clib(hypot.__name__, ctypes.pointer(out), lhs, rhs, ctypes.c_bool(batch)) return out @@ -75,7 +79,7 @@ def mod(lhs: AFArray, rhs: AFArray, /) -> AFArray: def neg(arr: AFArray) -> AFArray: - return sub(create_constant_array(0, (1,), float32), arr) + return sub(create_constant_array(0, (1,), dtype.c_api_value_to_dtype(wrapper.get_type(arr))), arr) def rem(lhs: AFArray, rhs: AFArray, /) -> AFArray: diff --git a/tests/test_numeric.py b/tests/test_numeric.py new file mode 100644 index 0000000..46715f0 --- /dev/null +++ b/tests/test_numeric.py @@ -0,0 +1,436 @@ +import random + +import numpy as np +import pytest + +import arrayfire_wrapper.dtypes as dtype +import arrayfire_wrapper.lib as wrapper +from tests.utility_functions import check_type_supported, get_all_types, get_real_types, get_complex_types + + +@pytest.mark.parametrize( + "shape", + [ + (), + (random.randint(1, 10),), + (random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + ], +) +@pytest.mark.parametrize("dtype_name", get_all_types()) +def test_abs_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None: + """Test absolute value operation between two arrays of the same shape""" + check_type_supported(dtype_name) + out = wrapper.randu(shape, dtype_name) + + result = wrapper.abs_(out) + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {dtype_name}" + + +@pytest.mark.parametrize( + "shape", + [ + (), + (random.randint(1, 10),), + (random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + ], +) +@pytest.mark.parametrize("dtype_name", get_all_types()) +def test_arg_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None: + """Test arg operation between two arrays of the same shape""" + check_type_supported(dtype_name) + out = wrapper.randu(shape, dtype_name) + + result = wrapper.arg(out) + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {dtype_name}" + + +@pytest.mark.parametrize( + "shape", + [ + (), + (random.randint(1, 10),), + (random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + ], +) +@pytest.mark.parametrize("dtype_name", get_real_types()) +def test_ceil_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None: + """Test ceil operation between two arrays of the same shape""" + check_type_supported(dtype_name) + out = wrapper.randu(shape, dtype_name) + + result = wrapper.ceil(out) + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {dtype_name}" + + +@pytest.mark.parametrize( + "invdtypes", + [ + dtype.c32, + ], +) +def test_ceil_shapes_invalid(invdtypes: dtype.Dtype) -> None: + """Test and_ operation between two arrays of the same shape""" + with pytest.raises(RuntimeError): + shape = (3, 3) + out = wrapper.randu(shape, invdtypes) + + result = wrapper.ceil(out) + + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {invdtypes}" + + +@pytest.mark.parametrize( + "shape", + [ + (), + (random.randint(1, 10),), + (random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + ], +) +@pytest.mark.parametrize("dtype_name", get_all_types()) +def test_maxof_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None: + """Test maxof operation between two arrays of the same shape""" + check_type_supported(dtype_name) + lhs = wrapper.randu(shape, dtype_name) + rhs = wrapper.randu(shape, dtype_name) + + result = wrapper.maxof(lhs, rhs) + + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {dtype_name}" + + +@pytest.mark.parametrize( + "shape_a, shape_b", + [ + ((1, 5), (5, 1)), # 2D with 2D inverse + ((5, 5), (5, 1)), # 2D with 2D + ((5, 5), (1, 1)), # 2D with 2D + ((1, 1, 1), (5, 5, 5)), # 3D with 3D + ((5,), (5,)), # 1D with 1D broadcast + ], +) +def test_maxof_varying_dimensionality(shape_a: tuple, shape_b: tuple) -> None: + """Test maxof with arrays of varying dimensionality.""" + lhs = wrapper.randu(shape_a, dtype.f32) + rhs = wrapper.randu(shape_b, dtype.f32) + + result = wrapper.maxof(lhs, rhs) + expected_shape = np.broadcast(np.empty(shape_a), np.empty(shape_b)).shape + assert ( + wrapper.get_dims(result)[0 : len(expected_shape)] == expected_shape # noqa + ), f"Failed for shapes {shape_a} and {shape_b}" + + +@pytest.mark.parametrize( + "shape", + [ + (), + (random.randint(1, 10),), + (random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + ], +) +@pytest.mark.parametrize("dtype_name", get_all_types()) +def test_minof_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None: + """Test minof operation between two arrays of the same shape""" + check_type_supported(dtype_name) + lhs = wrapper.randu(shape, dtype_name) + rhs = wrapper.randu(shape, dtype_name) + + result = wrapper.minof(lhs, rhs) + + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {dtype_name}" + + +@pytest.mark.parametrize( + "shape_a, shape_b", + [ + ((1, 5), (5, 1)), # 2D with 2D inverse + ((5, 5), (5, 1)), # 2D with 2D + ((5, 5), (1, 1)), # 2D with 2D + ((1, 1, 1), (5, 5, 5)), # 3D with 3D + ((5,), (5,)), # 1D with 1D broadcast + ], +) +def test_minof_varying_dimensionality(shape_a: tuple, shape_b: tuple) -> None: + """Test minof with arrays of varying dimensionality.""" + lhs = wrapper.randu(shape_a, dtype.f32) + rhs = wrapper.randu(shape_b, dtype.f32) + + result = wrapper.minof(lhs, rhs) + expected_shape = np.broadcast(np.empty(shape_a), np.empty(shape_b)).shape + assert ( + wrapper.get_dims(result)[0 : len(expected_shape)] == expected_shape # noqa + ), f"Failed for shapes {shape_a} and {shape_b}" + + +@pytest.mark.parametrize( + "shape", + [ + (), + (random.randint(1, 10),), + (random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + ], +) +@pytest.mark.parametrize("dtype_name", get_real_types()) +def test_mod_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None: + """Test mod operation between two arrays of the same shape""" + check_type_supported(dtype_name) + lhs = wrapper.randu(shape, dtype_name) + rhs = wrapper.randu(shape, dtype_name) + + result = wrapper.mod(lhs, rhs) + + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {dtype_name}" + + +@pytest.mark.parametrize("invdtypes", get_complex_types()) +def test_mod_shapes_invalid(invdtypes: dtype.Dtype) -> None: + """Test mod operation between two arrays of the same shape""" + with pytest.raises(RuntimeError): + shape = (3, 3) + lhs = wrapper.randu(shape, invdtypes) + rhs = wrapper.randu(shape, invdtypes) + + result = wrapper.mod(lhs, rhs) + + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {invdtypes}" + + +@pytest.mark.parametrize( + "shape", + [ + (), + (random.randint(1, 10),), + (random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + ], +) +@pytest.mark.parametrize("dtype_name", get_all_types()) +def test_neg_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None: + """Test arg operation between two arrays of the same shape""" + check_type_supported(dtype_name) + out = wrapper.randu(shape, dtype_name) + + result = wrapper.neg(out) + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {dtype_name}" + + +@pytest.mark.parametrize( + "shape", + [ + (), + (random.randint(1, 10),), + (random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + ], +) +@pytest.mark.parametrize("dtype_name", get_real_types()) +def test_rem_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None: + """Test remainder operation between two arrays of the same shape""" + check_type_supported(dtype_name) + lhs = wrapper.randu(shape, dtype_name) + rhs = wrapper.randu(shape, dtype_name) + + result = wrapper.rem(lhs, rhs) + + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {dtype_name}" + + +@pytest.mark.parametrize( + "shape", + [ + (), + (random.randint(1, 10),), + (random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + ], +) +@pytest.mark.parametrize("dtype_name", get_real_types()) +def test_round_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None: + """Test round operation between two arrays of the same shape""" + check_type_supported(dtype_name) + out = wrapper.randu(shape, dtype_name) + + result = wrapper.round_(out) + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {dtype_name}" + + +@pytest.mark.parametrize("invdtypes", get_complex_types()) +def test_round_shapes_invalid(invdtypes: dtype.Dtype) -> None: + """Test round operation between two arrays of the same shape""" + with pytest.raises(RuntimeError): + shape = (3, 3) + out = wrapper.randu(shape, invdtypes) + + result = wrapper.round_(out) + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {invdtypes}" + + +@pytest.mark.parametrize( + "shape", + [ + (), + (random.randint(1, 10),), + (random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + ], +) +@pytest.mark.parametrize("dtype_name", get_real_types()) +def test_sign_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None: + """Test round operation between two arrays of the same shape""" + check_type_supported(dtype_name) + out = wrapper.randu(shape, dtype_name) + + result = wrapper.sign(out) + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {dtype_name}" + + +@pytest.mark.parametrize("invdtypes", get_complex_types()) +def test_sign_shapes_invalid(invdtypes: dtype.Dtype) -> None: + """Test sign operation between two arrays of the same shape""" + with pytest.raises(RuntimeError): + shape = (3, 3) + out = wrapper.randu(shape, invdtypes) + + result = wrapper.sign(out) + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {invdtypes}" + + +@pytest.mark.parametrize( + "shape", + [ + (), + (random.randint(1, 10),), + (random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + ], +) +@pytest.mark.parametrize("dtype_name", get_real_types()) +def test_trunc_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None: + """Test truncating operation for an array with varying shape""" + check_type_supported(dtype_name) + out = wrapper.randu(shape, dtype_name) + + result = wrapper.trunc(out) + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {dtype_name}" + + +@pytest.mark.parametrize("invdtypes", get_complex_types()) +def test_trunc_shapes_invalid(invdtypes: dtype.Dtype) -> None: + """Test trunc operation for an array with varrying shape and invalid dtypes""" + with pytest.raises(RuntimeError): + shape = (3, 3) + out = wrapper.randu(shape, invdtypes) + + result = wrapper.trunc(out) + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {invdtypes}" + + +@pytest.mark.parametrize( + "shape", + [ + (), + (random.randint(1, 10),), + (random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + ], +) +def test_hypot_shape_dtypes(shape: tuple) -> None: + """Test hypotenuse operation between two arrays of the same shape""" + lhs = wrapper.randu(shape, dtype.f32) + rhs = wrapper.randu(shape, dtype.f32) + + result = wrapper.hypot(lhs, rhs, True) + + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {dtype.f32}" + + +@pytest.mark.parametrize( + "invdtypes", + [ + dtype.int32, + dtype.uint32, + ], +) +def test_hypot_unsupported_dtypes(invdtypes: dtype.Dtype) -> None: + """Test division operation for unsupported data types.""" + with pytest.raises(RuntimeError): + shape = (5, 5) + lhs = wrapper.randu(shape, invdtypes) + rhs = wrapper.randu(shape, invdtypes) + wrapper.hypot(rhs, lhs, True) + + +@pytest.mark.parametrize( + "shape", + [ + (), + (random.randint(1, 10),), + (random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)), + ], +) +@pytest.mark.parametrize("dtype_name", get_real_types()) +def test_clamp_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None: + """Test clamp operation between two arrays of the same shape""" + check_type_supported(dtype_name) + og = wrapper.randu(shape, dtype_name) + low = wrapper.randu(shape, dtype_name) + high = wrapper.randu(shape, dtype_name) + result = wrapper.clamp(og, low, high, False) + assert ( + wrapper.get_dims(result)[0 : len(shape)] == shape # noqa + ), f"failed for shape: {shape} and dtype {dtype_name}"