From 65198244912baf112badd20901954e399c659356 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 20 Sep 2023 02:15:35 +0300 Subject: [PATCH 01/16] Finish array creation functions --- arrayfire/array_api/_data_type_functions.py | 52 ++++++++++++++++++- arrayfire/array_object.py | 2 +- arrayfire/backend/_clib_wrapper/_base.py | 11 +++- .../backend/_clib_wrapper/_constant_array.py | 13 ++--- arrayfire/backend/_clib_wrapper/_operators.py | 6 +-- arrayfire/backend/_clib_wrapper/_random.py | 6 +-- .../_clib_wrapper/_reduction_operations.py | 10 ++-- arrayfire/backend/_clib_wrapper/_unsorted.py | 35 +++++++------ arrayfire/library/constants.py | 6 +++ arrayfire/library/operators.py | 2 +- 10 files changed, 102 insertions(+), 41 deletions(-) create mode 100644 arrayfire/library/constants.py diff --git a/arrayfire/array_api/_data_type_functions.py b/arrayfire/array_api/_data_type_functions.py index 5dc4d00..3f22d87 100755 --- a/arrayfire/array_api/_data_type_functions.py +++ b/arrayfire/array_api/_data_type_functions.py @@ -33,6 +33,36 @@ class finfo_object: dtype: Dtype +# TODO fix and add complex support +# float32 : finfo(32, 1.19209290 * 10^-7, 3.4028234 * 10^38, -3.4028234 * 10^38, 1.1754943 *10^−38, float32) +# float64 : finfo(64, 2.2204460492503131*10^-16, 1.7976931348623157 · 10^308, -1.7976931348623157 · 10^308, 2.2250738585072014 * 10^−308 , float64) +# float16 : finfo(16, 0.00097656, 65504, -65504, 0.00006103515625, float16) + + +# TODO separate API supported dtypes, add aliases +# Common +# int16 = Dtype("int16", "h", ctypes.c_short, "short int", 10) == s16 +# int32 = Dtype("int32", "i", ctypes.c_int, "int", 5) == s32 +# int64 = Dtype("int64", "l", ctypes.c_longlong, "long int", 8) == s64 +# uint8 = Dtype("uint8", "B", ctypes.c_ubyte, "unsigned_char", 7) == u8 +# uint16 = Dtype("uint16", "H", ctypes.c_ushort, "unsigned short int", 11) == u16 +# uint32 = Dtype("uint32", "I", ctypes.c_uint, "unsigned int", 6) == u32 +# uint64 = Dtype("uint64", "L", ctypes.c_ulonglong, "unsigned long int", 9) == u64 +# float16 = Dtype("float16", "e", ctypes.c_uint16, "half", 12) == f16 +# float32 = Dtype("float32", "f", ctypes.c_float, "float", 0) == f32 +# float64 = Dtype("float64", "d", ctypes.c_double, "double", 2) == f64 +# bool = Dtype("bool", "b", ctypes.c_char, "bool", 4) == b8 + +# AF API +# complex32 = Dtype("complex32", "F", ctypes.c_float * 2, "float complex", 1) == c32 +# complex64 = Dtype("complex64", "D", ctypes.c_double * 2, "double complex", 3) == c64 + +# Array API +# int8 = Dtype("int8", "b8", ctypes.c_char, "int8", 4) # HACK int8 - not supported in AF -> same as b8 +# complex64 = Dtype("complex64", "F", ctypes.c_float * 2, "float complex", 1) # type: ignore[arg-type] +# complex128 = Dtype("complex128", "D", ctypes.c_double * 2, "double complex", 3) # type: ignore[arg-type] + + @dataclass class iinfo_object: bits: int @@ -45,8 +75,26 @@ def finfo(type: Union[Dtype, Array], /) -> finfo_object: return NotImplemented -def iinfo(type: Union[Dtype, Array], /) -> iinfo_object: - return NotImplemented +# TODO fix bug +# def iinfo(type: Union[Dtype, Array], /) -> iinfo_object: +# # Reference: https://en.cppreference.com/w/cpp/language/types + +# if isinstance(type, Dtype): +# type_ = type +# elif isinstance(type, Array): +# type_ = Array.dtype +# else: +# raise ValueError("Wrong type.") + +# match type_: +# case int32: +# return iinfo_object(32, 2147483648, -2147483647, int32) +# case int16: +# return iinfo_object(16,32767, -32768, int16) +# case int8: +# return iinfo_object(8, 127, -128, int8) +# case int64: +# return iinfo_object(64, 9223372036854775807, -9223372036854775808, int64) def isdtype(dtype: Dtype, kind: Union[Dtype, str, Tuple[Union[Dtype, str], ...]]) -> bool: diff --git a/arrayfire/array_object.py b/arrayfire/array_object.py index 654f351..b4485d6 100755 --- a/arrayfire/array_object.py +++ b/arrayfire/array_object.py @@ -62,7 +62,7 @@ def __init__( offset: CType | None = None, strides: tuple[int, ...] | None = None, ) -> None: - self.arr = ctypes.c_void_p(0) # FIXME + self.arr = wrapper.AFArrayType.create_pointer() # FIXME _no_initial_dtype = False # HACK, FIXME if isinstance(dtype, str): diff --git a/arrayfire/backend/_clib_wrapper/_base.py b/arrayfire/backend/_clib_wrapper/_base.py index 92cbc67..94b31a9 100755 --- a/arrayfire/backend/_clib_wrapper/_base.py +++ b/arrayfire/backend/_clib_wrapper/_base.py @@ -1,3 +1,12 @@ +from __future__ import annotations + import ctypes -AFArrayType = ctypes.c_void_p +# AFArrayType = ctypes.c_void_p + + +class AFArrayType(ctypes.c_void_p): + @classmethod + def create_pointer(cls) -> AFArrayType: + cls.value = 0 + return cls() diff --git a/arrayfire/backend/_clib_wrapper/_constant_array.py b/arrayfire/backend/_clib_wrapper/_constant_array.py index f585b96..c2fcdaf 100755 --- a/arrayfire/backend/_clib_wrapper/_constant_array.py +++ b/arrayfire/backend/_clib_wrapper/_constant_array.py @@ -1,22 +1,19 @@ from __future__ import annotations import ctypes -from typing import TYPE_CHECKING from arrayfire.backend._backend import _backend from arrayfire.dtypes import CShape, Dtype, complex64, implicit_dtype, int64, is_complex_dtype, uint64 +from ._base import AFArrayType from ._error_handler import safe_call -if TYPE_CHECKING: - from ._base import AFArrayType - def _constant_complex(number: int | float | complex, shape: tuple[int, ...], dtype: Dtype, /) -> AFArrayType: """ source: https://arrayfire.org/docs/group__data__func__constant.htm#ga5a083b1f3cd8a72a41f151de3bdea1a2 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() c_shape = CShape(*shape) safe_call( @@ -36,7 +33,7 @@ def _constant_long(number: int | float, shape: tuple[int, ...], dtype: Dtype, /) """ source: https://arrayfire.org/docs/group__data__func__constant.htm#ga10f1c9fad1ce9e9fefd885d5a1d1fd49 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() c_shape = CShape(*shape) safe_call( @@ -51,7 +48,7 @@ def _constant_ulong(number: int | float, shape: tuple[int, ...], dtype: Dtype, / """ source: https://arrayfire.org/docs/group__data__func__constant.htm#ga67af670cc9314589f8134019f5e68809 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() c_shape = CShape(*shape) safe_call( @@ -66,7 +63,7 @@ def _constant(number: int | float, shape: tuple[int, ...], dtype: Dtype, /) -> A """ source: https://arrayfire.org/docs/group__data__func__constant.htm#gafc51b6a98765dd24cd4139f3bde00670 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() c_shape = CShape(*shape) safe_call( diff --git a/arrayfire/backend/_clib_wrapper/_operators.py b/arrayfire/backend/_clib_wrapper/_operators.py index e7e0e28..2e21c02 100755 --- a/arrayfire/backend/_clib_wrapper/_operators.py +++ b/arrayfire/backend/_clib_wrapper/_operators.py @@ -152,7 +152,7 @@ def clamp(arr: AFArrayType, /, lo: float, hi: float) -> AFArrayType: source: https://arrayfire.org/docs/group__arith__func__clamp.htm#gac4e785c5c877c7905e56f44ef0cb5e61 """ # TODO: check if lo and hi are of type float. Can be ArrayFire array as well - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_clamp(ctypes.pointer(out), arr, lo, hi)) return out @@ -522,12 +522,12 @@ def lnot(arr: AFArrayType, /) -> AFArrayType: def _binary_op(c_func: Callable, lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(c_func(ctypes.pointer(out), lhs, rhs, bcast_var.get())) return out def _unary_op(c_func: Callable, arr: AFArrayType, /) -> AFArrayType: - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(c_func(ctypes.pointer(out), arr)) return out diff --git a/arrayfire/backend/_clib_wrapper/_random.py b/arrayfire/backend/_clib_wrapper/_random.py index a7ba44d..b5d9c70 100644 --- a/arrayfire/backend/_clib_wrapper/_random.py +++ b/arrayfire/backend/_clib_wrapper/_random.py @@ -10,7 +10,7 @@ def create_random_engine(engine_type: int, seed: int, /) -> AFRandomEngine: - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_create_random_engine(ctypes.pointer(out), engine_type, ctypes.c_longlong(seed))) return out @@ -46,7 +46,7 @@ def randu(shape: tuple[int, ...], dtype: Dtype, /) -> AFArrayType: """ source: https://arrayfire.org/docs/group__random__func__randu.htm#ga412e2c2f5135bdda218c3487c487d3b5 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() c_shape = CShape(*shape) safe_call(_backend.clib.af_randu(ctypes.pointer(out), 4, c_shape.c_array, dtype.c_api_value)) return out @@ -56,7 +56,7 @@ def random_uniform(shape: tuple[int, ...], dtype: Dtype, engine: AFRandomEngine, """ source: https://arrayfire.org/docs/group__random__func__randu.htm#ga2ca76d970cfac076f9006755582a4a4c """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() c_shape = CShape(*shape) safe_call(_backend.clib.af_random_uniform(ctypes.pointer(out), 4, c_shape.c_array, dtype.c_api_value, engine)) return out diff --git a/arrayfire/backend/_clib_wrapper/_reduction_operations.py b/arrayfire/backend/_clib_wrapper/_reduction_operations.py index 8a341a3..4520f63 100755 --- a/arrayfire/backend/_clib_wrapper/_reduction_operations.py +++ b/arrayfire/backend/_clib_wrapper/_reduction_operations.py @@ -28,7 +28,7 @@ def all_true(arr: AFArrayType, axis: int, /) -> AFArrayType: """ source: https://arrayfire.org/docs/group__reduce__func__all__true.htm#ga068708be5177a0aa3788af140bb5ebd6 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_all_true(ctypes.pointer(out), arr, axis)) return out @@ -47,12 +47,12 @@ def any_true(arr: AFArrayType, axis: int, /) -> AFArrayType: """ source: https://arrayfire.org/docs/group__reduce__func__any__true.htm#ga7c275cda2cfc8eb0bd20ea86472ca0d5 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_all_true(ctypes.pointer(out), arr, axis)) return out -def any_true_all(arr: AFArrayType, /) -> complex: +def any_true_all(arr: AFArrayType, /) -> int | float | bool | complex: """ source: https://arrayfire.org/docs/group__reduce__func__any__true.htm#ga47d991276bb5bf8cdba8340e8751e536 """ @@ -66,7 +66,7 @@ def sum(arr: AFArrayType, axis: int, /) -> AFArrayType: """ source: https://arrayfire.org/docs/group__reduce__func__sum.htm#gacd4917c2e916870ebdf54afc2f61d533 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_sum(ctypes.pointer(out), arr, axis)) return out @@ -85,7 +85,7 @@ def sum_nan(arr: AFArrayType, axis: int, nan_value: float, /) -> AFArrayType: """ source: https://arrayfire.org/docs/group__reduce__func__sum.htm#ga52461231e2d9995f689b7f23eea0e798 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_sum_nan(ctypes.pointer(out), arr, axis, ctypes.c_double(nan_value))) return out diff --git a/arrayfire/backend/_clib_wrapper/_unsorted.py b/arrayfire/backend/_clib_wrapper/_unsorted.py index 7eee5a3..0d327a3 100755 --- a/arrayfire/backend/_clib_wrapper/_unsorted.py +++ b/arrayfire/backend/_clib_wrapper/_unsorted.py @@ -8,10 +8,11 @@ from arrayfire.dtypes import CShape, CType, Dtype, c_dim_t, to_str from arrayfire.library.device import PointerSource +from ._base import AFArrayType from ._error_handler import safe_call if TYPE_CHECKING: - from ._base import AFArrayType, _ArrayBuffer + from ._base import _ArrayBuffer # Array management @@ -20,7 +21,7 @@ def create_handle(shape: tuple[int, ...], dtype: Dtype, /) -> AFArrayType: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga3b8f5cf6fce69aa1574544bc2d44d7d0 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() c_shape = CShape(*shape) safe_call( @@ -35,7 +36,7 @@ def retain_array(arr: AFArrayType) -> AFArrayType: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga7ed45b3f881c0f6c80c5cf2af886dbab """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_retain_array(ctypes.pointer(out), arr)) return out @@ -45,7 +46,7 @@ def create_array(shape: tuple[int, ...], dtype: Dtype, array_buffer: _ArrayBuffe """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga834be32357616d8ab735087c6f681858 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() c_shape = CShape(*shape) safe_call( @@ -64,7 +65,7 @@ def device_array(shape: tuple[int, ...], dtype: Dtype, array_buffer: _ArrayBuffe """ source: https://arrayfire.org/docs/group__c__api__mat.htm#gaad4fc77f872217e7337cb53bfb623cf5 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() c_shape = CShape(*shape) safe_call( @@ -91,7 +92,7 @@ def create_strided_array( """ source: https://arrayfire.org/docs/group__internal__func__create.htm#gad31241a3437b7b8bc3cf49f85e5c4e0c """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() c_shape = CShape(*shape) if offset is None: @@ -211,7 +212,7 @@ def copy_array(arr: AFArrayType) -> AFArrayType: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga6040dc6f0eb127402fbf62c1165f0b9d """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_copy_array(ctypes.pointer(out), arr)) return out @@ -220,7 +221,7 @@ def cast(arr: AFArrayType, dtype: Dtype, /) -> AFArrayType: """ source: https://arrayfire.org/docs/group__arith__func__cast.htm#gab0cb307d6f9019ac8cbbbe9b8a4d6b9b """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_cast(ctypes.pointer(out), arr, dtype.c_api_value)) return out @@ -237,7 +238,7 @@ def index_gen( """ source: https://arrayfire.org/docs/group__index__func__index.htm#ga14a7d149dba0ed0b977335a3df9d91e6 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_index_gen(ctypes.pointer(out), arr, c_dim_t(ndims), indices.pointer)) return out @@ -246,7 +247,7 @@ def transpose(arr: AFArrayType, conjugate: bool, /) -> AFArrayType: """ https://arrayfire.org/docs/group__blas__func__transpose.htm#ga716b2b9bf190c8f8d0970aef2b57d8e7 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_transpose(ctypes.pointer(out), arr, conjugate)) return out @@ -255,7 +256,7 @@ def reorder(arr: AFArrayType, ndims: int, /) -> AFArrayType: """ source: https://arrayfire.org/docs/group__manip__func__reorder.htm#ga57383f4d00a3a86eab08dddd52c3ad3d """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() c_shape = CShape(*(tuple(reversed(range(ndims))) + tuple(range(ndims, 4)))) safe_call(_backend.clib.af_reorder(ctypes.pointer(out), arr, *c_shape)) return out @@ -278,7 +279,7 @@ def where(arr: AFArrayType) -> AFArrayType: """ source: https://arrayfire.org/docs/group__scan__func__where.htm#gafda59a3d25d35238592dd09907be9d07 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_where(ctypes.pointer(out), arr)) return out @@ -287,7 +288,7 @@ def af_range(shape: tuple[int, ...], axis: int, dtype: Dtype, /) -> AFArrayType: """ source: https://arrayfire.org/docs/group__data__func__range.htm#gadd6c9b479692454670a51e00ea5b26d5 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() c_shape = CShape(*shape) safe_call(_backend.clib.af_range(ctypes.pointer(out), 4, c_shape.c_array, axis, dtype.c_api_value)) return out @@ -297,7 +298,7 @@ def identity(shape: tuple[int, ...], dtype: Dtype, /) -> AFArrayType: """ source: """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() c_shape = CShape(*shape) safe_call(_backend.clib.af_identity(ctypes.pointer(out), 4, c_shape.c_array, dtype.c_api_value)) return out @@ -307,7 +308,7 @@ def flat(arr: AFArrayType, /) -> AFArrayType: """ source: https://arrayfire.org/docs/group__manip__func__flat.htm#gac6dfb22cbd3b151ddffb9a4ddf74455e """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_flat(ctypes.pointer(out), arr)) return out @@ -316,7 +317,7 @@ def assign_gen(lhs: AFArrayType, rhs: AFArrayType, ndims: int, indices: Any, /) """ source: https://arrayfire.org/docs/group__index__func__assign.htm#ga93cd5199c647dce0e3b823f063b352ae """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.af_assign_gen(ctypes.pointer(out), lhs, ndims, indices.pointer, rhs)) return out @@ -423,7 +424,7 @@ def get_stream(index: int) -> int: """ source: https://arrayfire.org/docs/group__cuda__mat.htm#ga8323b850f80afe9878b099f647b0a7e5 """ - out = ctypes.c_void_p(0) + out = AFArrayType.create_pointer() safe_call(_backend.clib.get().afcu_get_stream(ctypes.pointer(out), index)) return out.value diff --git a/arrayfire/library/constants.py b/arrayfire/library/constants.py new file mode 100644 index 0000000..0dcada0 --- /dev/null +++ b/arrayfire/library/constants.py @@ -0,0 +1,6 @@ +import math + +from arrayfire.backend._clib_wrapper._error_handler import constant +from arrayfire.dtypes import float64 + +pi = constant(math.pi, (1,), float64) diff --git a/arrayfire/library/operators.py b/arrayfire/library/operators.py index 954b1f6..c6685e1 100755 --- a/arrayfire/library/operators.py +++ b/arrayfire/library/operators.py @@ -136,7 +136,7 @@ def neq(x1: Array, x2: Array, /) -> Array: # # source: https://arrayfire.org/docs/group__arith__func__clamp.htm#gac4e785c5c877c7905e56f44ef0cb5e61 # # """ # # # TODO: check if lo and hi are of type float. Can be ArrayFire array as well -# # out = ctypes.c_void_p(0) +# # out = AFArrayType.create_pointer() # # safe_call(_backend.clib.af_clamp(ctypes.pointer(out), arr, lo, hi)) # # return out From a2571eec6d8544879cda6da65872af6d01b10ca1 Mon Sep 17 00:00:00 2001 From: Anton Date: Fri, 22 Sep 2023 23:15:06 +0300 Subject: [PATCH 02/16] More backend to arrayfire-python-wrapper --- arrayfire/__init__.py | 104 ++-- arrayfire/array_api/_dtypes.py | 28 +- arrayfire/array_object.py | 67 +-- arrayfire/backend/__init__.py | 0 arrayfire/backend/_backend.py | 240 -------- arrayfire/backend/_backend_functions.py | 304 ---------- arrayfire/backend/_clib_wrapper/__init__.py | 263 --------- arrayfire/backend/_clib_wrapper/_base.py | 12 - .../backend/_clib_wrapper/_constant_array.py | 90 --- .../backend/_clib_wrapper/_error_handler.py | 19 - arrayfire/backend/_clib_wrapper/_indexing.py | 258 --------- arrayfire/backend/_clib_wrapper/_operators.py | 533 ------------------ arrayfire/backend/_clib_wrapper/_random.py | 62 -- .../_clib_wrapper/_reduction_operations.py | 100 ---- arrayfire/backend/_clib_wrapper/_unsorted.py | 454 --------------- arrayfire/dtypes.py | 202 ++++--- arrayfire/library/_backend_functions.py | 305 ++++++++++ arrayfire/library/broadcast.py | 91 --- arrayfire/library/constant_array.py | 20 + arrayfire/library/constants.py | 7 +- arrayfire/library/data.py | 8 +- arrayfire/library/device.py | 5 +- arrayfire/library/old_operators.py | 310 ---------- arrayfire/library/operators.py | 21 +- arrayfire/library/random.py | 11 +- arrayfire/library/utils.py | 8 +- .../vector_algorithms/reduction_operations.py | 3 +- requirements.txt | 1 + tests/test_dtypes.py | 19 +- tests/test_random.py | 4 +- 30 files changed, 613 insertions(+), 2936 deletions(-) delete mode 100755 arrayfire/backend/__init__.py delete mode 100644 arrayfire/backend/_backend.py delete mode 100755 arrayfire/backend/_backend_functions.py delete mode 100755 arrayfire/backend/_clib_wrapper/__init__.py delete mode 100755 arrayfire/backend/_clib_wrapper/_base.py delete mode 100755 arrayfire/backend/_clib_wrapper/_constant_array.py delete mode 100755 arrayfire/backend/_clib_wrapper/_error_handler.py delete mode 100755 arrayfire/backend/_clib_wrapper/_indexing.py delete mode 100755 arrayfire/backend/_clib_wrapper/_operators.py delete mode 100644 arrayfire/backend/_clib_wrapper/_random.py delete mode 100755 arrayfire/backend/_clib_wrapper/_reduction_operations.py delete mode 100755 arrayfire/backend/_clib_wrapper/_unsorted.py create mode 100644 arrayfire/library/_backend_functions.py delete mode 100644 arrayfire/library/broadcast.py create mode 100644 arrayfire/library/constant_array.py delete mode 100644 arrayfire/library/old_operators.py diff --git a/arrayfire/__init__.py b/arrayfire/__init__.py index 9f64689..027d0d3 100755 --- a/arrayfire/__init__.py +++ b/arrayfire/__init__.py @@ -11,66 +11,88 @@ from .array_object import Array __all__ += [ - "int8", + "b8", + "bool", + "c32", + "c64", + "complex32", + "complex64", + "f16", + "f32", + "f64", + "float16", + "float32", + "float64", "int16", "int32", "int64", + "s16", + "s32", + "s64", + "u8", + "u16", + "u32", + "u64", "uint8", "uint16", "uint32", "uint64", - "float16", - "float32", - "float64", - "complex64", - "complex128", - "bool", ] from .dtypes import ( + b8, bool, + c32, + c64, + complex32, complex64, - complex128, + f16, + f32, + f64, float16, float32, float64, - int8, int16, int32, int64, + s16, + s32, + s64, + u8, + u16, + u32, + u64, uint8, uint16, uint32, uint64, ) -__all__ += [ - "get_backend", - "get_active_backend", # DeprecationWarning - "get_array_backend_name", - "get_array_device_id", - "get_available_backends", # DeprecationWarning - "get_backend_count", - "get_backend_id", # DeprecationWarning - "get_device_id", # DeprecationWarning - "get_dtype_size", - "get_size_of", # DeprecationWarning - "set_backend", -] +# __all__ += [ +# "get_active_backend", # DeprecationWarning +# "get_array_backend_name", +# "get_array_device_id", +# "get_available_backends", # DeprecationWarning +# "get_backend_count", +# "get_backend_id", # DeprecationWarning +# "get_device_id", # DeprecationWarning +# "get_dtype_size", +# "get_size_of", # DeprecationWarning +# "set_backend", +# ] -from .backend._backend import get_backend -from .backend._backend_functions import ( - get_active_backend, - get_array_backend_name, - get_array_device_id, - get_available_backends, - get_backend_count, - get_backend_id, - get_device_id, - get_dtype_size, - get_size_of, - set_backend, -) +# from .library._backend_functions import ( +# get_active_backend, +# get_array_backend_name, +# get_array_device_id, +# get_available_backends, +# get_backend_count, +# get_backend_id, +# get_device_id, +# get_dtype_size, +# get_size_of, +# set_backend, +# ) __all__ += [ "add", @@ -145,9 +167,9 @@ "root", "rsqrt", "sigmoid", - "land", - "lor", - "lnot", + "and_", + "or_", + "not_", ] @@ -156,6 +178,7 @@ acos, acosh, add, + and_, arg, asin, asinh, @@ -189,21 +212,20 @@ isinf, isnan, iszero, - land, le, lgamma, - lnot, log, log1p, log2, log10, - lor, lt, maxof, minof, mod, mul, neq, + not_, + or_, pow, pow2, real, diff --git a/arrayfire/array_api/_dtypes.py b/arrayfire/array_api/_dtypes.py index c2a5833..7903b56 100755 --- a/arrayfire/array_api/_dtypes.py +++ b/arrayfire/array_api/_dtypes.py @@ -1,5 +1,7 @@ from __future__ import annotations +import ctypes + __all__ = [ "all_dtypes", "boolean_dtypes", @@ -29,26 +31,16 @@ "uint64", ] -from typing import TYPE_CHECKING -from arrayfire import ( - bool, - complex64, - complex128, - float32, - float64, - int8, - int16, - int32, - int64, - uint8, - uint16, - uint32, - uint64, -) +from arrayfire import bool +from arrayfire import complex32 as afcomplex32 +from arrayfire import complex64 as afcomplex64 +from arrayfire import float32, float64, int16, int32, int64, uint8, uint16, uint32, uint64 +from arrayfire.dtypes import Dtype -if TYPE_CHECKING: - from arrayfire.dtypes import Dtype +int8 = Dtype("int8", "b8", ctypes.c_char, "int8", 4) # HACK int8 - not supported in AF -> same as b8 +complex64 = afcomplex32 +complex128 = afcomplex64 all_dtypes = ( int8, diff --git a/arrayfire/array_object.py b/arrayfire/array_object.py index b4485d6..bbc87ae 100755 --- a/arrayfire/array_object.py +++ b/arrayfire/array_object.py @@ -1,30 +1,22 @@ from __future__ import annotations -import array as py_array -import ctypes +import array as _pyarray from collections.abc import Callable -from dataclasses import dataclass from typing import TYPE_CHECKING, Any, ParamSpec, cast -from .backend import _clib_wrapper as wrapper -from .dtypes import CType, Dtype +import arrayfire_wrapper.lib as wrapper +from arrayfire_wrapper.defines import AFArray, ArrayBuffer, CType + +from .dtypes import Dtype from .dtypes import bool as afbool from .dtypes import c_api_value_to_dtype, float32, str_to_dtype +from .library.constant_array import create_constant_array from .library.device import PointerSource if TYPE_CHECKING: from ctypes import Array as CArray from enum import Enum -# TODO use int | float in operators -> remove bool | complex support - - -@dataclass(frozen=True) -class _ArrayBuffer: - address: int - length: int = 0 - - P = ParamSpec("P") @@ -55,14 +47,14 @@ def decorated(*args: P.args, **kwargs: P.kwargs) -> Array: class Array: def __init__( self, - obj: None | Array | py_array.array | int | wrapper.AFArrayType | list[int | float] = None, + obj: None | Array | _pyarray.array | int | AFArray | list[int | float] = None, dtype: None | Dtype | str = None, shape: tuple[int, ...] = (), to_device: bool = False, offset: CType | None = None, strides: tuple[int, ...] | None = None, ) -> None: - self.arr = wrapper.AFArrayType.create_pointer() # FIXME + self.arr = AFArray.create_null_pointer() _no_initial_dtype = False # HACK, FIXME if isinstance(dtype, str): @@ -84,27 +76,25 @@ def __init__( self.arr = wrapper.retain_array(obj.arr) return - if isinstance(obj, py_array.array): + if isinstance(obj, _pyarray.array): _type_char: str = obj.typecode - _array_buffer = _ArrayBuffer(*obj.buffer_info()) + _array_buffer = ArrayBuffer(*obj.buffer_info()) elif isinstance(obj, list): # TODO fix an issue when Array can not be created from float values to complex if _no_initial_dtype: arr_typecode = "f" - elif dtype.typecode in py_array.typecodes: + elif dtype.typecode in _pyarray.typecodes: arr_typecode = dtype.typecode else: raise TypeError(f"Unsupported typecode. Can not create a python array from '{repr(dtype)}'") - _array = py_array.array(arr_typecode, obj) + _array = _pyarray.array(arr_typecode, obj) _type_char = _array.typecode - _array_buffer = _ArrayBuffer(*_array.buffer_info()) + _array_buffer = ArrayBuffer(*_array.buffer_info()) - elif isinstance(obj, int) or isinstance(obj, wrapper.AFArrayType): - _array_buffer = _ArrayBuffer( - obj if not isinstance(obj, wrapper.AFArrayType) else obj.value # type: ignore[arg-type] - ) + elif isinstance(obj, int) or isinstance(obj, AFArray): + _array_buffer = ArrayBuffer(obj if not isinstance(obj, AFArray) else obj.value) # type: ignore[arg-type] if not shape: raise TypeError("Expected to receive the initial shape due to the obj being a data pointer.") @@ -805,15 +795,16 @@ def __setitem__(self, key: IndexKey, value: int | float | bool | Array, /) -> No dims = _get_processed_index(key, self.shape) if is_array_with_bool: ndims = 1 - other_arr = wrapper.create_constant_array(value, (int(num),), self.dtype) + # FIXME + other_arr = create_constant_array(value, (int(num),), self.dtype) # type: ignore[call-overload] else: - other_arr = wrapper.create_constant_array(value, dims, self.dtype) + other_arr = create_constant_array(value, dims, self.dtype) del_other = True else: other_arr = value.arr del_other = False - indices = wrapper.get_indices(key) + indices = wrapper.get_indices(key) # type: ignore[arg-type] # FIXME out = wrapper.assign_gen(self.arr, other_arr, ndims, indices) wrapper.release_array(self.arr) @@ -825,12 +816,12 @@ def __str__(self) -> str: # TODO change the look of array str. E.g., like np.array # if not _in_display_dims_limit(self.shape): # return _metadata_string(self.dtype, self.shape) - return _metadata_string(self.dtype) + wrapper.array_as_str(self.arr) + return _metadata_string(self.dtype) + _array_as_str(self) def __repr__(self) -> str: # return _metadata_string(self.dtype, self.shape) # TODO change the look of array representation. E.g., like np.array - return wrapper.array_as_str(self.arr) + return _array_as_str(self) def __del__(self) -> None: if not self.arr.value: @@ -855,7 +846,7 @@ def dtype(self) -> Dtype: out : Dtype Array data type. """ - return c_api_value_to_dtype(wrapper.get_ctype(self.arr)) + return c_api_value_to_dtype(wrapper.get_type(self.arr)) @property def device(self) -> Any: @@ -1012,9 +1003,9 @@ def copy(self) -> Array: return cast(Array, wrapper.copy_array(self.arr)) - @classmethod - def from_afarray(cls, array: wrapper.AFArrayType) -> None: - cls.arr = array + # @classmethod + # def from_afarray(cls, array: wrapper.AFArrayType) -> None: + # cls.arr = array IndexKey = int | float | complex | bool | wrapper.ParallelRange | slice | tuple[int | slice, ...] | Array @@ -1042,10 +1033,10 @@ def _process_c_function(lhs: int | float | Array, rhs: int | float | Array, c_fu elif isinstance(lhs, Array) and isinstance(rhs, (int, float)): lhs_array = lhs.arr - rhs_array = wrapper.create_constant_array(rhs, lhs.shape, lhs.dtype) + rhs_array = create_constant_array(rhs, lhs.shape, lhs.dtype) elif isinstance(lhs, (int, float)) and isinstance(rhs, Array): - lhs_array = wrapper.create_constant_array(lhs, rhs.shape, rhs.dtype) + lhs_array = create_constant_array(lhs, rhs.shape, rhs.dtype) rhs_array = rhs.arr else: @@ -1096,3 +1087,7 @@ def _slice_to_length(key: slice, dim: int) -> int: step = 1 return int(((stop - start - 1) / step) + 1) + + +def _array_as_str(array: Array) -> str: + return wrapper.array_to_string("", array.arr, 4, True) diff --git a/arrayfire/backend/__init__.py b/arrayfire/backend/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/arrayfire/backend/_backend.py b/arrayfire/backend/_backend.py deleted file mode 100644 index b151679..0000000 --- a/arrayfire/backend/_backend.py +++ /dev/null @@ -1,240 +0,0 @@ -__all__ = ["BackendType"] - -import ctypes -import enum -import os -import platform -import sys -from dataclasses import dataclass -from enum import Enum -from pathlib import Path -from typing import Iterator - -from arrayfire.logger import logger -from arrayfire.version import ARRAYFIRE_VER_MAJOR - - -def is_arch_x86() -> bool: - machine = platform.machine() - return platform.architecture()[0][0:2] == "32" and (machine[-2:] == "86" or machine[0:3] == "arm") - - -class _LibPrefixes(Enum): - forge = "" - arrayfire = "af" - - -class _SupportedPlatforms(Enum): - windows = "Windows" - darwin = "Darwin" # OSX - linux = "Linux" - - @classmethod - def is_cygwin(cls, name: str) -> bool: - return "cyg" in name.lower() - - -@dataclass(frozen=True) -class _BackendPathConfig: - lib_prefix: str - lib_postfix: str - af_path: Path - cuda_found: bool - - def __iter__(self) -> Iterator: - return iter((self.lib_prefix, self.lib_postfix, self.af_path, self.af_path, self.cuda_found)) - - -def _get_backend_path_config() -> _BackendPathConfig: - platform_name = platform.system() - cuda_found = False - - try: - af_path = Path(os.environ["AF_PATH"]) - except KeyError: - af_path = None - - try: - cuda_path = Path(os.environ["CUDA_PATH"]) - except KeyError: - cuda_path = None - - if platform_name == _SupportedPlatforms.windows.value or _SupportedPlatforms.is_cygwin(platform_name): - if platform_name == _SupportedPlatforms.windows.value: - # HACK Supressing crashes caused by missing dlls - # http://stackoverflow.com/questions/8347266/missing-dll-print-message-instead-of-launching-a-popup - # https://msdn.microsoft.com/en-us/_clib/windows/desktop/ms680621.aspx - ctypes.windll.kernel32.SetErrorMode(0x0001 | 0x0002) # type: ignore[attr-defined] - - if not af_path: - af_path = _find_default_path(f"C:/Program Files/ArrayFire/v{ARRAYFIRE_VER_MAJOR}") - - if cuda_path and (cuda_path / "bin").is_dir() and (cuda_path / "nvvm/bin").is_dir(): - cuda_found = True - - return _BackendPathConfig("", ".dll", af_path, cuda_found) - - if platform_name == _SupportedPlatforms.darwin.value: - default_cuda_path = Path("/usr/local/cuda/") - - if not af_path: - af_path = _find_default_path("/opt/arrayfire", "/usr/local") - - if not (cuda_path and default_cuda_path.exists()): - cuda_found = (default_cuda_path / "lib").is_dir() and (default_cuda_path / "/nvvm/lib").is_dir() - - return _BackendPathConfig("lib", f".{ARRAYFIRE_VER_MAJOR}.dylib", af_path, cuda_found) - - if platform_name == _SupportedPlatforms.linux.value: - default_cuda_path = Path("/usr/local/cuda/") - - if not af_path: - af_path = _find_default_path(f"/opt/arrayfire-{ARRAYFIRE_VER_MAJOR}", "/opt/arrayfire/", "/usr/local/") - - if not (cuda_path and default_cuda_path.exists()): - if "64" in platform.architecture()[0]: # Check either is 64 bit arch is selected - cuda_found = (default_cuda_path / "lib64").is_dir() and (default_cuda_path / "nvvm/lib64").is_dir() - else: - cuda_found = (default_cuda_path / "lib").is_dir() and (default_cuda_path / "nvvm/lib").is_dir() - - return _BackendPathConfig("lib", f".so.{ARRAYFIRE_VER_MAJOR}", af_path, cuda_found) - - raise OSError(f"{platform_name} is not supported.") - - -def _find_default_path(*args: str) -> Path: - for path in args: - default_path = Path(path) - if default_path.exists(): - return default_path - raise ValueError("None of specified default paths were found.") - - -class BackendType(enum.Enum): # TODO change name - avoid using _backend_type - e.g. type - unified = 0 # NOTE It is set as Default value on Arrayfire backend - cpu = 1 - cuda = 2 - opencl = 4 - oneapi = 8 - - def __iter__(self) -> Iterator: - # NOTE cpu comes last because we want to keep this order priorty during backend initialization - return iter((self.unified, self.cuda, self.opencl, self.cpu)) - - -class Backend: - _backend_type: BackendType - # HACK for osx - # _backend.clib = ctypes.CDLL("/opt/arrayfire//lib/libafcpu.3.dylib") - # HACK for windows - # _backend.clib = ctypes.CDLL("C:/Program Files/ArrayFire/v3/lib/afcpu.dll") - _clib: ctypes.CDLL - - def __init__(self) -> None: - self._backend_path_config = _get_backend_path_config() - - self._load_forge_lib() - self._load_backend_libs() - - def _load_forge_lib(self) -> None: - for lib_name in self._lib_names("forge", _LibPrefixes.forge): - try: - ctypes.cdll.LoadLibrary(str(lib_name)) - logger.info(f"Loaded {lib_name}") - break - except OSError: - logger.warning(f"Unable to load {lib_name}") - pass - - def _load_backend_libs(self) -> None: - for backend_type in BackendType: - self._load_backend_lib(backend_type) - - if self._backend_type: - logger.info(f"Setting {backend_type.name} as backend.") - break - - if not self._backend_type and not self._clib: - raise RuntimeError( - "Could not load any ArrayFire libraries.\n" - "Please look at https://github.com/arrayfire/arrayfire-python/wiki for more information." - ) - - def _load_backend_lib(self, _backend_type: BackendType) -> None: - # NOTE we still set unified cdll to it's original name later, even if the path search is different - name = _backend_type.name if _backend_type != BackendType.unified else "" - - for lib_name in self._lib_names(name, _LibPrefixes.arrayfire): - try: - ctypes.cdll.LoadLibrary(str(lib_name)) - self._backend_type = _backend_type - self._clib = ctypes.CDLL(str(lib_name)) - - if _backend_type == BackendType.cuda: - self._load_nvrtc_builtins_lib(lib_name.parent) - - logger.info(f"Loaded {lib_name}") - break - except OSError: - logger.warning(f"Unable to load {lib_name}") - pass - - def _load_nvrtc_builtins_lib(self, lib_path: Path) -> None: - nvrtc_name = self._find_nvrtc_builtins_lib_name(lib_path) - if nvrtc_name: - ctypes.cdll.LoadLibrary(str(lib_path / nvrtc_name)) - logger.info(f"Loaded {lib_path / nvrtc_name}") - else: - logger.warning("Could not find local nvrtc-builtins library") - - def _lib_names(self, name: str, lib: _LibPrefixes, ver_major: str | None = None) -> list[Path]: - post = self._backend_path_config.lib_postfix if ver_major is None else ver_major - lib_name = self._backend_path_config.lib_prefix + lib.value + name + post - - lib64_path = self._backend_path_config.af_path / "lib64" - search_path = lib64_path if lib64_path.is_dir() else self._backend_path_config.af_path / "lib" - - site_path = Path(sys.prefix) / "lib64" if not is_arch_x86() else Path(sys.prefix) / "lib" - - # prefer locally packaged arrayfire libraries if they exist - af_module = __import__(__name__) - local_path = Path(af_module.__path__[0]) if af_module.__path__ else Path("") - - lib_paths = [Path("", lib_name), site_path / lib_name, local_path / lib_name] - - if self._backend_path_config.af_path: # prefer specified AF_PATH if exists - return [search_path / lib_name] + lib_paths - else: - lib_paths.insert(2, Path(str(search_path), lib_name)) - return lib_paths - - def _find_nvrtc_builtins_lib_name(self, search_path: Path) -> str | None: - for f in search_path.iterdir(): - if "nvrtc-builtins" in f.name: - return f.name - return None - - @property - def backend_type(self) -> BackendType: - return self._backend_type - - @property - def clib(self) -> ctypes.CDLL: - return self._clib - - -# Initialize the backend -_backend = Backend() - - -def get_backend() -> Backend: - """ - Get the current active backend. - - Returns - ------- - value : Backend - Current active backend. - """ - - return _backend diff --git a/arrayfire/backend/_backend_functions.py b/arrayfire/backend/_backend_functions.py deleted file mode 100755 index dd64530..0000000 --- a/arrayfire/backend/_backend_functions.py +++ /dev/null @@ -1,304 +0,0 @@ -from __future__ import annotations - -import warnings -from enum import Enum -from typing import TYPE_CHECKING - -from ._backend import Backend, BackendType, get_backend -from ._clib_wrapper._unsorted import cublas_set_math_mode -from ._clib_wrapper._unsorted import get_backend_count as c_get_backend_count -from ._clib_wrapper._unsorted import get_backend_id as c_get_backend_id -from ._clib_wrapper._unsorted import get_device_id as c_get_device_id -from ._clib_wrapper._unsorted import get_native_id as c_get_native_id -from ._clib_wrapper._unsorted import get_size_of as c_get_size_of -from ._clib_wrapper._unsorted import get_stream as c_get_stream -from ._clib_wrapper._unsorted import set_backend as c_set_backend -from ._clib_wrapper._unsorted import set_native_id as c_set_native_id - -if TYPE_CHECKING: - from arrayfire import Array - from arrayfire.dtypes import Dtype - - -class CublasMathMode(Enum): - default = 0 - tensor_op = 1 - - -def set_backend(backend_type: BackendType | str) -> None: - """ - Set a specific backend by backend_type name. - - Parameters - ---------- - backend_type : BackendType | str - Name of the backend type to set. - - Raises - ------ - ValueError - If the given backend_type name is not a valid name for backend backend_type. - TypeError - If the given backend_type is not a valid type for backend backend_type. - RuntimeError - If the given backend_type is already the active backend backend_type. - RuntimeError - If the given backend_type could not be set as new backend backend_type. - """ - backend = get_backend() - current_active_backend_type = backend.backend_type - - if isinstance(backend_type, str): - if backend_type not in [d.name for d in BackendType]: - raise ValueError(f"{backend_type} is not a valid name for backend backend_type.") - backend_type = BackendType[backend_type] - - if not isinstance(backend_type, BackendType): - raise TypeError(f"{backend_type} is not a valid type for backend backend_type.") - - if current_active_backend_type == backend_type: - raise RuntimeError(f"{backend_type} is already the active backend backend_type.") - - if backend.backend_type == BackendType.unified: - c_set_backend(backend_type.value) - - # NOTE keep in mind that this operation works in-place - # FIXME should not access private API - backend._load_backend_lib(backend_type) - - if current_active_backend_type == backend.backend_type: - raise RuntimeError(f"Could not set {backend_type} as new backend backend_type. Consider checking logs.") - - -def get_array_backend_name(array: Array) -> str: - """ - Get the name of the backend on which the Array is located. - - Parameters - ---------- - array : Array - The Array to get the backend name of. - - Returns - ------- - value : str - Name of the backend on which the Array is located. - """ - - id_ = c_get_backend_id(array.arr) - return BackendType(id_).name - - -def get_backend_id(array: Array) -> str: - warnings.warn("Was renamed. Now get_array_backend_name() in main repo.", DeprecationWarning) - return get_array_backend_name(array) - - -def get_backend_count() -> int: - """ - Get a number of available backends. - - Returns - ------- - - value : int - Number of available backends. - """ - - return c_get_backend_count() - - -def get_active_backend() -> Backend: - """ - Get the current active backend. - - value : Backend - Current active backend. - """ - - # TODO do not deprecate - warnings.warn("A user has access explicitly only to the active backend.", DeprecationWarning) - return get_backend() - - -def get_available_backends() -> Backend: - """ - Get the list of available backends. - - Returns - ------- - value : Backend - Current active backend. - """ - - # TODO do not deprecate - warnings.warn( - "A user has access explicitly only to the active backend. Thus returning only active backend.", - DeprecationWarning, - ) - return get_active_backend() - - -def get_array_device_id(array: Array) -> int: - """ - Get the id of the device on which the Array was created. - - Parameters - ---------- - array : Array - The Array to get the device id of. - - Returns - ------- - value : int - The id of the device on which the Array was created. - """ - - return c_get_device_id(array.arr) - - -def get_device_id(array: Array) -> int: - warnings.warn("Was renamed due to unintuitive function name. Now get_array_device_id().", DeprecationWarning) - return get_array_device_id(array) - - -def get_dtype_size(dtype: Dtype) -> int: - """ - Get the size of the type represented by Dtype. - - Parameters - ---------- - dtype : Dtype - The type to get the size of. - - Returns - ------- - value : int - The size of the type in bytes. - """ - - return c_get_size_of(dtype) - - -def get_size_of(dtype: Dtype) -> int: - warnings.warn("Was renamed due to unintuitive function name. Now get_dtype_size().", DeprecationWarning) - return get_dtype_size(dtype) - - -# Previously module arrayfire.cuda - - -def _check_if_cuda_used() -> None: - backend = get_backend() - if backend.backend_type != BackendType.cuda: - raise RuntimeError( - f"Can not get the CUDA stream id because the other backend is in use: {backend.backend_type}." - ) - - -def get_stream(index: int) -> int: - warnings.warn("Was renamed due to unintuitive function name. Now get_cuda_stream().", DeprecationWarning) - return get_cuda_stream(index) - - -def get_cuda_stream(index: int) -> int: - """ - Get the CUDA stream used for the device id by ArrayFire. - - Parameters - ---------- - idx : int - Specifies the index of the device. - - Returns - ------- - value : int - Denoting the stream id. - - Raises - ------ - RuntimeError - If the current backend type is not CUDA. - """ - _check_if_cuda_used() - - return c_get_stream(index) - - -def get_native_id(index: int) -> int: - warnings.warn("Was renamed due to unintuitive function name. Now get_native_cuda_id().", DeprecationWarning) - return get_native_cuda_id(index) - - -def get_native_cuda_id(index: int) -> int: - """ - Get native (unsorted) CUDA device id. - - Parameters - ---------- - idx : int - Specifies the (sorted) index of the device. - - Returns - ------- - value : int - Denoting the native cuda id. - - Raises - ------ - RuntimeError - If the current backend type is not CUDA. - """ - _check_if_cuda_used() - - return c_get_native_id(index) - - -def set_native_id(index: int) -> None: - warnings.warn("Was renamed due to unintuitive function name. Now get_native_cuda_id().", DeprecationWarning) - return set_native_cuda_id(index) - - -def set_native_cuda_id(index: int) -> None: - """ - Set native (unsorted) CUDA device id. - - Parameters - ---------- - idx : int - Specifies the (unsorted) native index of the device. - - Raises - ------ - RuntimeError - If the current backend type is not CUDA. - """ - _check_if_cuda_used() - - return c_set_native_id(index) - - -def set_cublas_mode(mode: CublasMathMode | int = CublasMathMode.default) -> None: - """ - Set cuBLAS math mode for CUDA backend. It enables the Tensor Core usage if available on CUDA backend GPUs. - - Parameters - ---------- - mode : CublasMathMode | int - Specify the mode available within CublasMathMode enum. - - Raises - ------ - ValueError - If the given math mode int value is not a valid value for cuBLAS math mode. - RuntimeError - If the current backend type is not CUDA. - """ - if isinstance(mode, int): - if mode not in [m.value for m in CublasMathMode]: - raise ValueError(f"{mode} is not supported as cublas math mode.") - mode = CublasMathMode(mode) - - _check_if_cuda_used() - - return cublas_set_math_mode(mode.value) diff --git a/arrayfire/backend/_clib_wrapper/__init__.py b/arrayfire/backend/_clib_wrapper/__init__.py deleted file mode 100755 index f409341..0000000 --- a/arrayfire/backend/_clib_wrapper/__init__.py +++ /dev/null @@ -1,263 +0,0 @@ -# flake8: noqa - -__all__ = ["AFArrayType"] - -from ._base import AFArrayType - -__all__ += [ - "add", - "sub", - "mul", - "div", - "mod", - "pow", - "bitnot", - "bitand", - "bitor", - "bitxor", - "bitshiftl", - "bitshiftr", - "lt", - "le", - "gt", - "ge", - "eq", - "neq", - "sin", - "cos", - "tan", - "asin", - "acos", - "atan", - "atan2", - "sinh", - "cosh", - "tanh", - "asinh", - "acosh", - "atanh", - "exp", - "expm1", - "log", - "log1p", - "log2", - "log10", - "sqrt", - "cbrt", - "hypot", - "erf", - "erfc", - "tgamma", - "lgamma", - "pow2", - "sign", - "abs", - "ceil", - "floor", - "round", - "trunc", - "isinf", - "isnan", - "iszero", - "isinf", - "isnan", - "iszero", - "isinf", - "isnan", - "clamp", - "arg", - "conjg", - "cplx1", - "cplx2", - "imag", - "factorial", - "maxof", - "minof", - "real", - "rem", - "root", - "rsqrt", - "sigmoid", - "land", - "lor", - "lnot", -] - -from ._operators import ( - abs, - acos, - acosh, - add, - arg, - asin, - asinh, - atan, - atan2, - atanh, - bitand, - bitnot, - bitor, - bitshiftl, - bitshiftr, - bitxor, - cbrt, - ceil, - clamp, - conjg, - cos, - cosh, - cplx1, - cplx2, - div, - eq, - erf, - erfc, - exp, - expm1, - factorial, - floor, - ge, - gt, - hypot, - imag, - isinf, - isnan, - iszero, - land, - le, - lgamma, - lnot, - log, - log1p, - log2, - log10, - lor, - lt, - maxof, - minof, - mod, - mul, - neq, - pow, - pow2, - real, - rem, - root, - round, - rsqrt, - sigmoid, - sign, - sin, - sinh, - sqrt, - sub, - tan, - tanh, - tgamma, - trunc, -) - -__all__ += [ - "create_array", - "create_handle", - "create_strided_array", - "device_array", - "get_ctype", - "get_elements", - "get_numdims", - "retain_array", - "get_dims", - "get_scalar", - "is_empty", - "get_data_ptr", - "copy_array", - "index_gen", - "transpose", - "reorder", - "array_as_str", - "where", - "get_last_error", - "set_backend", - "get_backend_count", - "get_device_id", - "get_size_of", - "get_backend_id", - "af_range", - "identity", - "flat", -] - -from ._unsorted import ( - af_range, - array_as_str, - assign_gen, - cast, - copy_array, - create_array, - create_handle, - create_strided_array, - device_array, - flat, - get_backend_count, - get_backend_id, - get_ctype, - get_data_ptr, - get_device, - get_device_id, - get_dims, - get_elements, - get_last_error, - get_numdims, - get_offset, - get_scalar, - get_size_of, - get_strides, - identity, - index_gen, - is_empty, - release_array, - reorder, - retain_array, - set_backend, - sync, - transpose, - where, -) - -__all__ += ["safe_call"] - -from ._error_handler import safe_call - -__all__ += ["count_all"] - -from ._reduction_operations import ( - all_true, - all_true_all, - any_true, - any_true_all, - count_all, - sum, - sum_all, - sum_nan, - sum_nan_all, -) - -__all__ += ["create_constant_array"] - -from ._constant_array import create_constant_array - -__all__ += ["get_indices"] -from ._indexing import ParallelRange, get_indices - -__all__ += ["create_random_engine", "release_random_engine", "AFRandomEngine"] -from ._random import ( - AFRandomEngine, - create_random_engine, - random_engine_get_seed, - random_engine_get_type, - random_engine_set_seed, - random_engine_set_type, - random_uniform, - randu, - release_random_engine, -) diff --git a/arrayfire/backend/_clib_wrapper/_base.py b/arrayfire/backend/_clib_wrapper/_base.py deleted file mode 100755 index 94b31a9..0000000 --- a/arrayfire/backend/_clib_wrapper/_base.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import ctypes - -# AFArrayType = ctypes.c_void_p - - -class AFArrayType(ctypes.c_void_p): - @classmethod - def create_pointer(cls) -> AFArrayType: - cls.value = 0 - return cls() diff --git a/arrayfire/backend/_clib_wrapper/_constant_array.py b/arrayfire/backend/_clib_wrapper/_constant_array.py deleted file mode 100755 index c2fcdaf..0000000 --- a/arrayfire/backend/_clib_wrapper/_constant_array.py +++ /dev/null @@ -1,90 +0,0 @@ -from __future__ import annotations - -import ctypes - -from arrayfire.backend._backend import _backend -from arrayfire.dtypes import CShape, Dtype, complex64, implicit_dtype, int64, is_complex_dtype, uint64 - -from ._base import AFArrayType -from ._error_handler import safe_call - - -def _constant_complex(number: int | float | complex, shape: tuple[int, ...], dtype: Dtype, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__data__func__constant.htm#ga5a083b1f3cd8a72a41f151de3bdea1a2 - """ - out = AFArrayType.create_pointer() - c_shape = CShape(*shape) - - safe_call( - _backend.clib.af_constant_complex( - ctypes.pointer(out), - ctypes.c_double(number.real), - ctypes.c_double(number.imag), - 4, - ctypes.pointer(c_shape.c_array), - dtype.c_api_value, - ) - ) - return out - - -def _constant_long(number: int | float, shape: tuple[int, ...], dtype: Dtype, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__data__func__constant.htm#ga10f1c9fad1ce9e9fefd885d5a1d1fd49 - """ - out = AFArrayType.create_pointer() - c_shape = CShape(*shape) - - safe_call( - _backend.clib.af_constant_long( - ctypes.pointer(out), ctypes.c_longlong(int(number.real)), 4, ctypes.pointer(c_shape.c_array) - ) - ) - return out - - -def _constant_ulong(number: int | float, shape: tuple[int, ...], dtype: Dtype, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__data__func__constant.htm#ga67af670cc9314589f8134019f5e68809 - """ - out = AFArrayType.create_pointer() - c_shape = CShape(*shape) - - safe_call( - _backend.clib.af_constant_ulong( - ctypes.pointer(out), ctypes.c_ulonglong(int(number.real)), 4, ctypes.pointer(c_shape.c_array) - ) - ) - return out - - -def _constant(number: int | float, shape: tuple[int, ...], dtype: Dtype, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__data__func__constant.htm#gafc51b6a98765dd24cd4139f3bde00670 - """ - out = AFArrayType.create_pointer() - c_shape = CShape(*shape) - - safe_call( - _backend.clib.af_constant( - ctypes.pointer(out), ctypes.c_double(number), 4, ctypes.pointer(c_shape.c_array), dtype.c_api_value - ) - ) - return out - - -def create_constant_array(number: int | float | complex, shape: tuple[int, ...], dtype: Dtype, /) -> AFArrayType: - if not dtype: - dtype = implicit_dtype(number, dtype) - - if isinstance(number, complex): - return _constant_complex(number, shape, dtype if is_complex_dtype(dtype) else complex64) - - if dtype == int64: - return _constant_long(number, shape, dtype) - - if dtype == uint64: - return _constant_ulong(number, shape, dtype) - - return _constant(number, shape, dtype) diff --git a/arrayfire/backend/_clib_wrapper/_error_handler.py b/arrayfire/backend/_clib_wrapper/_error_handler.py deleted file mode 100755 index b6b61fa..0000000 --- a/arrayfire/backend/_clib_wrapper/_error_handler.py +++ /dev/null @@ -1,19 +0,0 @@ -import ctypes -from enum import Enum - -from arrayfire.backend._backend import _backend -from arrayfire.dtypes import c_dim_t, to_str - - -class _ErrorCodes(Enum): - none = 0 - - -def safe_call(c_err: int) -> None: - if c_err == _ErrorCodes.none.value: - return - - err_str = ctypes.c_char_p(0) - err_len = c_dim_t(0) - _backend.clib.af_get_last_error(ctypes.pointer(err_str), ctypes.pointer(err_len)) # BUG somewhere - raise RuntimeError(to_str(err_str)) diff --git a/arrayfire/backend/_clib_wrapper/_indexing.py b/arrayfire/backend/_clib_wrapper/_indexing.py deleted file mode 100755 index a2d9d08..0000000 --- a/arrayfire/backend/_clib_wrapper/_indexing.py +++ /dev/null @@ -1,258 +0,0 @@ -from __future__ import annotations - -import ctypes -import math -from typing import Any - -from arrayfire.backend._backend import _backend -from arrayfire.library.broadcast import bcast_var - -from ._error_handler import safe_call - - -class _IndexSequence(ctypes.Structure): - """ - arrayfire equivalent of slice - - Attributes - ---------- - - begin: number - Start of the sequence. - - end : number - End of sequence. - - step : number - Step size. - - Parameters - ---------- - - chunk: slice or number. - - """ - - # More about _fields_ purpose: https://docs.python.org/3/library/ctypes.html#structures-and-unions - _fields_ = [("begin", ctypes.c_double), ("end", ctypes.c_double), ("step", ctypes.c_double)] - - def __init__(self, chunk: int | slice): - self.begin = ctypes.c_double(0) - self.end = ctypes.c_double(-1) - self.step = ctypes.c_double(1) - - if isinstance(chunk, int): - self.begin = ctypes.c_double(chunk) - self.end = ctypes.c_double(chunk) - - elif isinstance(chunk, slice): - if chunk.step: - self.step = ctypes.c_double(chunk.step) - if chunk.step < 0: - self.begin, self.end = self.end, self.begin - - if chunk.start: - self.begin = ctypes.c_double(chunk.start) - - if chunk.stop: - self.end = ctypes.c_double(chunk.stop) - - # handle special cases - if 0 <= self.end <= self.begin and self.step >= 0: # type: ignore[operator] - self.begin.value = 1 - self.end.value = 1 - self.step.value = 1 - - elif self.begin <= self.end < 0 and self.step <= 0: # type: ignore[operator] - self.begin.value = -2 - self.end.value = -2 - self.step.value = -1 - - if chunk.stop: - self.end -= math.copysign(1, self.step) # type: ignore[operator, assignment, arg-type] # FIXME - else: - raise IndexError("Invalid type while indexing arrayfire.array") - - -class ParallelRange(_IndexSequence): - - """ - Class used to parallelize for loop. - - Inherits from _IndexSequence. - - Attributes - ---------- - - chunk: slice - - Parameters - ---------- - - start: number - Beginning of parallel range. - - stop : number - End of parallel range. - - step : number - Step size for parallel range. - - Examples - -------- - - >>> import arrayfire as af - >>> a = af.randu(3, 3) - >>> b = af.randu(3, 1) - >>> c = af.constant(0, 3, 3) - >>> for ii in af.ParallelRange(3): - ... c[:, ii] = a[:, ii] + b - ... - >>> af.display(a) - [3 3 1 1] - 0.4107 0.1794 0.3775 - 0.8224 0.4198 0.3027 - 0.9518 0.0081 0.6456 - - >>> af.display(b) - [3 1 1 1] - 0.7269 - 0.7104 - 0.5201 - - >>> af.display(c) - [3 3 1 1] - 1.1377 0.9063 1.1045 - 1.5328 1.1302 1.0131 - 1.4719 0.5282 1.1657 - - """ - - def __init__(self, start: int | float, stop: int | float | None = None, step: int | float | None = None) -> None: - if not stop: - stop = start - start = 0 - - self.chunk = slice(start, stop, step) - super().__init__(self.chunk) - - def __iter__(self) -> ParallelRange: - return self - - def next(self) -> ParallelRange: - """ - Function called by the iterator in Python 2 - """ - if bcast_var.get() is True: - bcast_var.toggle() - raise StopIteration - else: - bcast_var.toggle() - return self - - def __next__(self) -> ParallelRange: - """ - Function called by the iterator in Python 3 - """ - return self.next() - - -class _IndexUnion(ctypes.Union): - _fields_ = [("arr", ctypes.c_void_p), ("seq", _IndexSequence)] - - -class IndexStructure(ctypes.Structure): - _fields_ = [("idx", _IndexUnion), ("isSeq", ctypes.c_bool), ("isBatch", ctypes.c_bool)] - - """ - Container for the index class in arrayfire C library - - Attributes - ---------- - idx.arr: ctypes.c_void_p - - Default 0 - - idx.seq: af._IndexSequence - - Default af._IndexSequence(0, -1, 1) - - isSeq : bool - - Default True - - isBatch : bool - - Default False - - Parameters - ----------- - - idx: key - - If of type af.Array, self.idx.arr = idx, self.isSeq = False - - If of type af.ParallelRange, self.idx.seq = idx, self.isBatch = True - - Default:, self.idx.seq = af._IndexSequence(idx) - - Note - ---- - - Implemented for internal use only. Use with extreme caution. - - """ - - def __init__(self, idx: Any) -> None: - self.idx = _IndexUnion() - self.isBatch = False - self.isSeq = True - - # FIXME cyclic reimport - # if isinstance(idx, Array): - # if idx.dtype == af_bool: - # self.idx.arr = everything.where(idx.arr) - # else: - # self.idx.arr = everything.retain_array(idx.arr) - - # self.isSeq = False - - if isinstance(idx, ParallelRange): - self.idx.seq = idx - self.isBatch = True - - else: - self.idx.seq = _IndexSequence(idx) - - def __del__(self) -> None: - if not self.isSeq: - # ctypes field variables are automatically - # converted to basic C types so we have to - # build the void_p from the value again. - arr = ctypes.c_void_p(self.idx.arr) - safe_call(_backend.clib.af_release_array(arr)) - - -class CIndexStructure: - def __init__(self) -> None: - index_vec = IndexStructure * 4 - # NOTE Do not lose those idx as self.array keeps no reference to them. Otherwise the destructor - # is prematurely called - self.idxs = [IndexStructure(slice(None))] * 4 - self.array = index_vec(*self.idxs) - - @property - def pointer(self) -> ctypes._Pointer: - return ctypes.pointer(self.array) - - def __getitem__(self, idx: int) -> IndexStructure: - return self.array[idx] - - def __setitem__(self, idx: int, value: IndexStructure) -> None: - self.array[idx] = value - self.idxs[idx] = value - - -def get_indices(key: int | slice | tuple[int | slice, ...]) -> CIndexStructure: # BUG - indices = CIndexStructure() - - if isinstance(key, tuple): - for n in range(len(key)): - indices[n] = IndexStructure(key[n]) - else: - indices[0] = IndexStructure(key) - - return indices diff --git a/arrayfire/backend/_clib_wrapper/_operators.py b/arrayfire/backend/_clib_wrapper/_operators.py deleted file mode 100755 index 2e21c02..0000000 --- a/arrayfire/backend/_clib_wrapper/_operators.py +++ /dev/null @@ -1,533 +0,0 @@ -from __future__ import annotations - -import ctypes -from collections.abc import Callable -from typing import TYPE_CHECKING - -from arrayfire.backend._backend import _backend -from arrayfire.library.broadcast import bcast_var - -from ._error_handler import safe_call - -if TYPE_CHECKING: - from ._base import AFArrayType - -# Arithmetic Operators - - -def add(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__add.htm#ga1dfbee755fedd680f4476803ddfe06a7 - """ - return _binary_op(_backend.clib.af_add, lhs, rhs) - - -def sub(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__sub.htm#ga80ff99a2e186c23614ea9f36ffc6f0a4 - """ - return _binary_op(_backend.clib.af_sub, lhs, rhs) - - -def mul(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__mul.htm#ga5f7588b2809ff7551d38b6a0bd583a02 - """ - return _binary_op(_backend.clib.af_mul, lhs, rhs) - - -def div(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__div.htm#ga21f3f97755702692ec8976934e75fde6 - """ - return _binary_op(_backend.clib.af_div, lhs, rhs) - - -def mod(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__mod.htm#ga01924d1b59d8886e46fabd2dc9b27e0f - """ - return _binary_op(_backend.clib.af_mod, lhs, rhs) - - -def pow(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__pow.htm#ga0f28be1a9c8b176a78c4a47f483e7fc6 - """ - return _binary_op(_backend.clib.af_pow, lhs, rhs) - - -# Bitwise Operators - - -def bitnot(arr: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__bitnot.htm#gaf97e8a38aab59ed2d3a742515467d01e - """ - return _unary_op(_backend.clib.af_bitnot, arr) - - -def bitand(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__bitand.htm#ga45c0779ade4703708596df11cca98800 - """ - return _binary_op(_backend.clib.af_bitand, lhs, rhs) - - -def bitor(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__bitor.htm#ga84c99f77d1d83fd53f949b4d67b5b210 - """ - return _binary_op(_backend.clib.af_bitor, lhs, rhs) - - -def bitxor(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__bitxor.htm#ga8188620da6b432998e55fdd1fad22100 - """ - return _binary_op(_backend.clib.af_bitxor, lhs, rhs) - - -def bitshiftl(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__shiftl.htm#ga3139645aafe6f045a5cab454e9c13137 - """ - return _binary_op(_backend.clib.af_bitshiftl, lhs, rhs) - - -def bitshiftr(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__shiftr.htm#ga4c06b9977ecf96cdfc83b5dfd1ac4895 - """ - return _binary_op(_backend.clib.af_bitshiftr, lhs, rhs) - - -def lt(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/arith_8h.htm#ae7aa04bf23b32bb11c4bab8bdd637103 - """ - return _binary_op(_backend.clib.af_lt, lhs, rhs) - - -def le(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__le.htm#gad5535ce64dbed46d0773fd494e84e922 - """ - return _binary_op(_backend.clib.af_le, lhs, rhs) - - -def gt(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__gt.htm#ga4e65603259515de8939899a163ebaf9e - """ - return _binary_op(_backend.clib.af_gt, lhs, rhs) - - -def ge(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__ge.htm#ga4513f212e0b0a22dcf4653e89c85e3d9 - """ - return _binary_op(_backend.clib.af_ge, lhs, rhs) - - -def eq(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__eq.htm#ga76d2da7716831616bb81effa9e163693 - """ - return _binary_op(_backend.clib.af_eq, lhs, rhs) - - -def neq(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__neq.htm#gae4ee8bd06a410f259f1493fb811ce441 - """ - return _binary_op(_backend.clib.af_neq, lhs, rhs) - - -# Numeric Functions - - -def clamp(arr: AFArrayType, /, lo: float, hi: float) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__clamp.htm#gac4e785c5c877c7905e56f44ef0cb5e61 - """ - # TODO: check if lo and hi are of type float. Can be ArrayFire array as well - out = AFArrayType.create_pointer() - safe_call(_backend.clib.af_clamp(ctypes.pointer(out), arr, lo, hi)) - return out - - -def minof(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__min.htm#ga2b842c2d86df978ff68699aeaafca794 - """ - return _binary_op(_backend.clib.af_minof, lhs, rhs) - - -def maxof(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__max.htm#ga0cd47e70cf82b48730a97c59f494b421 - """ - return _binary_op(_backend.clib.af_maxof, lhs, rhs) - - -def rem(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__clamp.htm#gac4e785c5c877c7905e56f44ef0cb5e61 - """ - return _binary_op(_backend.clib.af_rem, lhs, rhs) - - -def abs(arr: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__abs.htm#ga7e8b3c848e6cda3d1f3b0c8b2b4c3f8f - """ - return _unary_op(_backend.clib.af_abs, arr) - - -def arg(arr: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__arg.htm#gad04de0f7948688378dcd3628628a7424 - """ - return _unary_op(_backend.clib.af_arg, arr) - - -def sign(arr: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__sign.htm#ga2d55dfb9b25e0a1316b70f01d5b44b35 - """ - return _unary_op(_backend.clib.af_sign, arr) - - -def round(arr: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__sign.htm#ga2d55dfb9b25e0a1316b70f01d5b44b35 - """ - return _unary_op(_backend.clib.af_round, arr) - - -def trunc(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_trunc, arr) - - -def floor(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_floor, arr) - - -def ceil(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_ceil, arr) - - -def hypot(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _binary_op(_backend.clib.af_hypot, lhs, rhs) - - -def sin(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_sin, arr) - - -def cos(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_cos, arr) - - -def tan(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_tan, arr) - - -def asin(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_asin, arr) - - -def acos(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_acos, arr) - - -def atan(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_atan, arr) - - -def atan2(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _binary_op(_backend.clib.af_atan2, lhs, rhs) - - -def cplx1(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_cplx, arr) - - -def cplx2(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _binary_op(_backend.clib.af_cplx2, lhs, rhs) - - -def real(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_real, arr) - - -def imag(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_imag, arr) - - -def conjg(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_conjg, arr) - - -def sinh(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_sinh, arr) - - -def cosh(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_cosh, arr) - - -def tanh(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_tanh, arr) - - -def asinh(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_asinh, arr) - - -def acosh(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_acosh, arr) - - -def atanh(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_atanh, arr) - - -def root(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _binary_op(_backend.clib.af_root, lhs, rhs) - - -def pow2(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_pow2, arr) - - -def sigmoid(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_sigmoid, arr) - - -def exp(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_exp, arr) - - -def expm1(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_expm1, arr) - - -def erf(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_erf, arr) - - -def erfc(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_erfc, arr) - - -def log(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_log, arr) - - -def log1p(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_log1p, arr) - - -def log10(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_log10, arr) - - -def log2(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_log2, arr) - - -def sqrt(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_sqrt, arr) - - -def rsqrt(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_rsqrt, arr) - - -def cbrt(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_cbrt, arr) - - -def factorial(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_factorial, arr) - - -def tgamma(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_tgamma, arr) - - -def lgamma(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_lgamma, arr) - - -def iszero(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_iszero, arr) - - -def isinf(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_isinf, arr) - - -def isnan(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_isnan, arr) - - -def land(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _binary_op(_backend.clib.af_and, lhs, rhs) - - -def lor(lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _binary_op(_backend.clib.af_or, lhs, rhs) - - -def lnot(arr: AFArrayType, /) -> AFArrayType: - """ - source: - """ - return _unary_op(_backend.clib.af_not, arr) - - -def _binary_op(c_func: Callable, lhs: AFArrayType, rhs: AFArrayType, /) -> AFArrayType: - out = AFArrayType.create_pointer() - safe_call(c_func(ctypes.pointer(out), lhs, rhs, bcast_var.get())) - return out - - -def _unary_op(c_func: Callable, arr: AFArrayType, /) -> AFArrayType: - out = AFArrayType.create_pointer() - safe_call(c_func(ctypes.pointer(out), arr)) - return out diff --git a/arrayfire/backend/_clib_wrapper/_random.py b/arrayfire/backend/_clib_wrapper/_random.py deleted file mode 100644 index b5d9c70..0000000 --- a/arrayfire/backend/_clib_wrapper/_random.py +++ /dev/null @@ -1,62 +0,0 @@ -import ctypes - -from arrayfire.backend._backend import _backend -from arrayfire.dtypes import CShape, Dtype - -from ._base import AFArrayType -from ._error_handler import safe_call - -AFRandomEngine = ctypes.c_void_p - - -def create_random_engine(engine_type: int, seed: int, /) -> AFRandomEngine: - out = AFArrayType.create_pointer() - safe_call(_backend.clib.af_create_random_engine(ctypes.pointer(out), engine_type, ctypes.c_longlong(seed))) - return out - - -def release_random_engine(engine: AFRandomEngine, /) -> None: - safe_call(_backend.clib.af_release_random_engine(engine)) - return None - - -def random_engine_set_type(engine: AFRandomEngine, engine_type: int, /) -> None: - safe_call(_backend.clib.af_random_engine_set_type(ctypes.pointer(engine), engine_type)) - return None - - -def random_engine_get_type(engine: AFRandomEngine, /) -> int: - out = ctypes.c_int(0) - safe_call(_backend.clib.af_random_engine_get_type(ctypes.pointer(out), engine)) - return out.value - - -def random_engine_set_seed(engine: AFRandomEngine, seed: int, /) -> None: - safe_call(_backend.clib.af_random_engine_set_seed(ctypes.pointer(engine), ctypes.c_longlong(seed))) - return None - - -def random_engine_get_seed(engine: AFRandomEngine, /) -> int: - out = ctypes.c_longlong(0) - safe_call(_backend.clib.af_random_engine_get_seed(ctypes.pointer(out), engine)) - return out.value - - -def randu(shape: tuple[int, ...], dtype: Dtype, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__random__func__randu.htm#ga412e2c2f5135bdda218c3487c487d3b5 - """ - out = AFArrayType.create_pointer() - c_shape = CShape(*shape) - safe_call(_backend.clib.af_randu(ctypes.pointer(out), 4, c_shape.c_array, dtype.c_api_value)) - return out - - -def random_uniform(shape: tuple[int, ...], dtype: Dtype, engine: AFRandomEngine, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__random__func__randu.htm#ga2ca76d970cfac076f9006755582a4a4c - """ - out = AFArrayType.create_pointer() - c_shape = CShape(*shape) - safe_call(_backend.clib.af_random_uniform(ctypes.pointer(out), 4, c_shape.c_array, dtype.c_api_value, engine)) - return out diff --git a/arrayfire/backend/_clib_wrapper/_reduction_operations.py b/arrayfire/backend/_clib_wrapper/_reduction_operations.py deleted file mode 100755 index 4520f63..0000000 --- a/arrayfire/backend/_clib_wrapper/_reduction_operations.py +++ /dev/null @@ -1,100 +0,0 @@ -from __future__ import annotations - -import ctypes -from collections.abc import Callable -from typing import TYPE_CHECKING - -from arrayfire.backend._backend import _backend - -from ._error_handler import safe_call - -if TYPE_CHECKING: - from ._base import AFArrayType - - -def count_all(x: AFArrayType) -> int | float | complex: - # TODO reconsider original arith.count - return _reduce_all(x, _backend.clib.af_count_all) - - -def _reduce_all(arr: AFArrayType, c_func: Callable) -> int | float | complex: - real = ctypes.c_double(0) - imag = ctypes.c_double(0) - safe_call(c_func(ctypes.pointer(real), ctypes.pointer(imag), arr)) - return real.value if imag.value == 0 else real.value + imag.value * 1j - - -def all_true(arr: AFArrayType, axis: int, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__reduce__func__all__true.htm#ga068708be5177a0aa3788af140bb5ebd6 - """ - out = AFArrayType.create_pointer() - safe_call(_backend.clib.af_all_true(ctypes.pointer(out), arr, axis)) - return out - - -def all_true_all(arr: AFArrayType, /) -> complex: - """ - source: https://arrayfire.org/docs/group__reduce__func__all__true.htm#ga068708be5177a0aa3788af140bb5ebd6 - """ - real = ctypes.c_double(0) - imag = ctypes.c_double(0) - safe_call(_backend.clib.af_all_true(ctypes.pointer(real), ctypes.pointer(imag), arr)) - return real.value # NOTE imag is always set to 0 in C library - - -def any_true(arr: AFArrayType, axis: int, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__reduce__func__any__true.htm#ga7c275cda2cfc8eb0bd20ea86472ca0d5 - """ - out = AFArrayType.create_pointer() - safe_call(_backend.clib.af_all_true(ctypes.pointer(out), arr, axis)) - return out - - -def any_true_all(arr: AFArrayType, /) -> int | float | bool | complex: - """ - source: https://arrayfire.org/docs/group__reduce__func__any__true.htm#ga47d991276bb5bf8cdba8340e8751e536 - """ - real = ctypes.c_double(0) - imag = ctypes.c_double(0) - safe_call(_backend.clib.af_all_true(ctypes.pointer(real), ctypes.pointer(imag), arr)) - return real.value # NOTE imag is always set to 0 in C library - - -def sum(arr: AFArrayType, axis: int, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__reduce__func__sum.htm#gacd4917c2e916870ebdf54afc2f61d533 - """ - out = AFArrayType.create_pointer() - safe_call(_backend.clib.af_sum(ctypes.pointer(out), arr, axis)) - return out - - -def sum_all(arr: AFArrayType, /) -> complex: - """ - source: https://arrayfire.org/docs/group__reduce__func__sum.htm#gabc009d04df0faf29ba1e381c7badde58 - """ - real = ctypes.c_double(0) - imag = ctypes.c_double(0) - safe_call(_backend.clib.af_sum_all(ctypes.pointer(real), ctypes.pointer(imag), arr)) - return real.value # NOTE imag is always set to 0 in C library - - -def sum_nan(arr: AFArrayType, axis: int, nan_value: float, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__reduce__func__sum.htm#ga52461231e2d9995f689b7f23eea0e798 - """ - out = AFArrayType.create_pointer() - safe_call(_backend.clib.af_sum_nan(ctypes.pointer(out), arr, axis, ctypes.c_double(nan_value))) - return out - - -def sum_nan_all(arr: AFArrayType, nan_value: float, /) -> complex: - """ - source: https://arrayfire.org/docs/group__reduce__func__sum.htm#gabc009d04df0faf29ba1e381c7badde58 - """ - real = ctypes.c_double(0) - imag = ctypes.c_double(0) - safe_call(_backend.clib.af_sum_all(ctypes.pointer(real), ctypes.pointer(imag), arr, ctypes.c_double(nan_value))) - return real.value # NOTE imag is always set to 0 in C library diff --git a/arrayfire/backend/_clib_wrapper/_unsorted.py b/arrayfire/backend/_clib_wrapper/_unsorted.py deleted file mode 100755 index 0d327a3..0000000 --- a/arrayfire/backend/_clib_wrapper/_unsorted.py +++ /dev/null @@ -1,454 +0,0 @@ -from __future__ import annotations - -import ctypes -from typing import TYPE_CHECKING, Any -from typing import cast as typing_cast - -from arrayfire.backend._backend import _backend -from arrayfire.dtypes import CShape, CType, Dtype, c_dim_t, to_str -from arrayfire.library.device import PointerSource - -from ._base import AFArrayType -from ._error_handler import safe_call - -if TYPE_CHECKING: - from ._base import _ArrayBuffer - -# Array management - - -def create_handle(shape: tuple[int, ...], dtype: Dtype, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__c__api__mat.htm#ga3b8f5cf6fce69aa1574544bc2d44d7d0 - """ - out = AFArrayType.create_pointer() - c_shape = CShape(*shape) - - safe_call( - _backend.clib.af_create_handle( - ctypes.pointer(out), c_shape.original_shape, ctypes.pointer(c_shape.c_array), dtype.c_api_value - ) - ) - return out - - -def retain_array(arr: AFArrayType) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__c__api__mat.htm#ga7ed45b3f881c0f6c80c5cf2af886dbab - """ - out = AFArrayType.create_pointer() - - safe_call(_backend.clib.af_retain_array(ctypes.pointer(out), arr)) - return out - - -def create_array(shape: tuple[int, ...], dtype: Dtype, array_buffer: _ArrayBuffer, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__c__api__mat.htm#ga834be32357616d8ab735087c6f681858 - """ - out = AFArrayType.create_pointer() - c_shape = CShape(*shape) - - safe_call( - _backend.clib.af_create_array( - ctypes.pointer(out), - ctypes.c_void_p(array_buffer.address), - c_shape.original_shape, - ctypes.pointer(c_shape.c_array), - dtype.c_api_value, - ) - ) - return out - - -def device_array(shape: tuple[int, ...], dtype: Dtype, array_buffer: _ArrayBuffer, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__c__api__mat.htm#gaad4fc77f872217e7337cb53bfb623cf5 - """ - out = AFArrayType.create_pointer() - c_shape = CShape(*shape) - - safe_call( - _backend.clib.af_device_array( - ctypes.pointer(out), - ctypes.c_void_p(array_buffer.address), - c_shape.original_shape, - ctypes.pointer(c_shape.c_array), - dtype.c_api_value, - ) - ) - return out - - -def create_strided_array( - shape: tuple[int, ...], - dtype: Dtype, - array_buffer: _ArrayBuffer, - offset: CType, - strides: tuple[int, ...], - pointer_source: PointerSource, - /, -) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__internal__func__create.htm#gad31241a3437b7b8bc3cf49f85e5c4e0c - """ - out = AFArrayType.create_pointer() - c_shape = CShape(*shape) - - if offset is None: - offset = c_dim_t(0) - - if strides is None: - strides = (1, c_shape[0], c_shape[0] * c_shape[1], c_shape[0] * c_shape[1] * c_shape[2]) - - if len(strides) < 4: - strides += (strides[-1],) * (4 - len(strides)) - - safe_call( - _backend.clib.af_create_strided_array( - ctypes.pointer(out), - ctypes.c_void_p(array_buffer.address), - offset, - c_shape.original_shape, - ctypes.pointer(c_shape.c_array), - CShape(*strides).c_array, - dtype.c_api_value, - pointer_source.value, - ) - ) - return out - - -def get_ctype(arr: AFArrayType) -> int: - """ - source: https://arrayfire.org/docs/group__c__api__mat.htm#ga0dda6898e1c0d9a43efb56cd6a988c9b - """ - out = ctypes.c_int(0) - - safe_call(_backend.clib.af_get_type(ctypes.pointer(out), arr)) - return out.value - - -def get_elements(arr: AFArrayType) -> int: - """ - source: https://arrayfire.org/docs/group__c__api__mat.htm#ga6845bbe4385a60a606b88f8130252c1f - """ - out = c_dim_t(0) - - safe_call(_backend.clib.af_get_elements(ctypes.pointer(out), arr)) - return out.value - - -def get_numdims(arr: AFArrayType) -> int: - """ - source: https://arrayfire.org/docs/group__c__api__mat.htm#gaefa019d932ff58c2a829ce87edddd2a8 - """ - out = ctypes.c_uint(0) - - safe_call(_backend.clib.af_get_numdims(ctypes.pointer(out), arr)) - return out.value - - -def get_dims(arr: AFArrayType) -> tuple[int, ...]: - """ - source: https://arrayfire.org/docs/group__c__api__mat.htm#ga8b90da50a532837d9763e301b2267348 - """ - d0 = c_dim_t(0) - d1 = c_dim_t(0) - d2 = c_dim_t(0) - d3 = c_dim_t(0) - - safe_call( - _backend.clib.af_get_dims(ctypes.pointer(d0), ctypes.pointer(d1), ctypes.pointer(d2), ctypes.pointer(d3), arr) - ) - return (d0.value, d1.value, d2.value, d3.value) - - -def get_strides(arr: AFArrayType) -> tuple[int, ...]: - """ - source: https://arrayfire.org/docs/group__internal__func__strides.htm#gaff91b376156ce0ad7180af6e68faab51 - """ - s0 = c_dim_t(0) - s1 = c_dim_t(0) - s2 = c_dim_t(0) - s3 = c_dim_t(0) - safe_call( - _backend.clib.af_get_strides( - ctypes.pointer(s0), ctypes.pointer(s1), ctypes.pointer(s2), ctypes.pointer(s3), arr - ) - ) - return (s0.value, s1.value, s2.value, s3.value) - - -def get_scalar(arr: AFArrayType, dtype: Dtype, /) -> int | float | complex | bool | None: - """ - source: https://arrayfire.org/docs/group__c__api__mat.htm#gaefe2e343a74a84bd43b588218ecc09a3 - """ - out = dtype.c_type() - safe_call(_backend.clib.af_get_scalar(ctypes.pointer(out), arr)) - return typing_cast(int | float | complex | bool | None, out.value) - - -def is_empty(arr: AFArrayType) -> bool: - """ - source: https://arrayfire.org/docs/group__c__api__mat.htm#ga19c749e95314e1c77d816ad9952fb680 - """ - out = ctypes.c_bool() - safe_call(_backend.clib.af_is_empty(ctypes.pointer(out), arr)) - return out.value - - -def get_data_ptr(arr: AFArrayType, size: int, dtype: Dtype, /) -> ctypes.Array: - """ - source: https://arrayfire.org/docs/group__c__api__mat.htm#ga6040dc6f0eb127402fbf62c1165f0b9d - """ - c_shape = dtype.c_type * size - ctypes_array = c_shape() - safe_call(_backend.clib.af_get_data_ptr(ctypes.pointer(ctypes_array), arr)) - return ctypes_array - - -def copy_array(arr: AFArrayType) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__c__api__mat.htm#ga6040dc6f0eb127402fbf62c1165f0b9d - """ - out = AFArrayType.create_pointer() - safe_call(_backend.clib.af_copy_array(ctypes.pointer(out), arr)) - return out - - -def cast(arr: AFArrayType, dtype: Dtype, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__arith__func__cast.htm#gab0cb307d6f9019ac8cbbbe9b8a4d6b9b - """ - out = AFArrayType.create_pointer() - safe_call(_backend.clib.af_cast(ctypes.pointer(out), arr, dtype.c_api_value)) - return out - - -# Arrayfire Functions - - -def index_gen( - arr: AFArrayType, - ndims: int, - indices: Any, - /, -) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__index__func__index.htm#ga14a7d149dba0ed0b977335a3df9d91e6 - """ - out = AFArrayType.create_pointer() - safe_call(_backend.clib.af_index_gen(ctypes.pointer(out), arr, c_dim_t(ndims), indices.pointer)) - return out - - -def transpose(arr: AFArrayType, conjugate: bool, /) -> AFArrayType: - """ - https://arrayfire.org/docs/group__blas__func__transpose.htm#ga716b2b9bf190c8f8d0970aef2b57d8e7 - """ - out = AFArrayType.create_pointer() - safe_call(_backend.clib.af_transpose(ctypes.pointer(out), arr, conjugate)) - return out - - -def reorder(arr: AFArrayType, ndims: int, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__manip__func__reorder.htm#ga57383f4d00a3a86eab08dddd52c3ad3d - """ - out = AFArrayType.create_pointer() - c_shape = CShape(*(tuple(reversed(range(ndims))) + tuple(range(ndims, 4)))) - safe_call(_backend.clib.af_reorder(ctypes.pointer(out), arr, *c_shape)) - return out - - -def array_as_str(arr: AFArrayType) -> str: - """ - source: - - https://arrayfire.org/docs/group__print__func__tostring.htm#ga01f32ef2420b5d4592c6e4b4964b863b - - https://arrayfire.org/docs/group__device__func__free__host.htm#ga3f1149a837a7ebbe8002d5d2244e3370 - """ - arr_str = ctypes.c_char_p(0) - safe_call(_backend.clib.af_array_to_string(ctypes.pointer(arr_str), "", arr, 4, True)) - py_str = to_str(arr_str) - safe_call(_backend.clib.af_free_host(arr_str)) - return py_str - - -def where(arr: AFArrayType) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__scan__func__where.htm#gafda59a3d25d35238592dd09907be9d07 - """ - out = AFArrayType.create_pointer() - safe_call(_backend.clib.af_where(ctypes.pointer(out), arr)) - return out - - -def af_range(shape: tuple[int, ...], axis: int, dtype: Dtype, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__data__func__range.htm#gadd6c9b479692454670a51e00ea5b26d5 - """ - out = AFArrayType.create_pointer() - c_shape = CShape(*shape) - safe_call(_backend.clib.af_range(ctypes.pointer(out), 4, c_shape.c_array, axis, dtype.c_api_value)) - return out - - -def identity(shape: tuple[int, ...], dtype: Dtype, /) -> AFArrayType: - """ - source: - """ - out = AFArrayType.create_pointer() - c_shape = CShape(*shape) - safe_call(_backend.clib.af_identity(ctypes.pointer(out), 4, c_shape.c_array, dtype.c_api_value)) - return out - - -def flat(arr: AFArrayType, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__manip__func__flat.htm#gac6dfb22cbd3b151ddffb9a4ddf74455e - """ - out = AFArrayType.create_pointer() - safe_call(_backend.clib.af_flat(ctypes.pointer(out), arr)) - return out - - -def assign_gen(lhs: AFArrayType, rhs: AFArrayType, ndims: int, indices: Any, /) -> AFArrayType: - """ - source: https://arrayfire.org/docs/group__index__func__assign.htm#ga93cd5199c647dce0e3b823f063b352ae - """ - out = AFArrayType.create_pointer() - safe_call(_backend.clib.af_assign_gen(ctypes.pointer(out), lhs, ndims, indices.pointer, rhs)) - return out - - -def release_array(arr: AFArrayType, /) -> None: - """ - source: https://arrayfire.org/docs/group__c__api__mat.htm#gad6c58648ed0db398e170dabf045e8309 - """ - safe_call(_backend.clib.af_release_array(arr)) - - -def get_offset(arr: AFArrayType, /) -> int: - """ - source: https://arrayfire.org/docs/group__internal__func__offset.htm#ga303cb334026bdb5cab86e038951d6a5a - """ - out = c_dim_t(0) - safe_call(_backend.clib.af_get_offset(ctypes.pointer(out), arr)) - return out.value - - -# Safe Call Wrapper - - -def get_last_error() -> ctypes.c_char_p: - """ - source: https://arrayfire.org/docs/exception_8h.htm#a4f0227c17954d343021313f77e695c8e - """ - out = ctypes.c_char_p(0) - _backend.clib.af_get_last_error(ctypes.pointer(out), ctypes.pointer(c_dim_t(0))) - return out - - -# Device - - -# FIXME -def sync(device_id: int) -> None: - """ - source: https://arrayfire.org/docs/group__device__func__sync.htm#ga9dbc7f1e99d70170ad567c480b6ddbde - """ - safe_call(_backend.clib.af_sync(device_id)) - - -def get_device() -> int: - """ - source: https://arrayfire.org/docs/group__device__func__set.htm#ga54120b126cfcb1b0b3ee25e0fc66b8a4 - """ - out = ctypes.c_int(0) - safe_call(_backend.clib.af_get_device(ctypes.pointer(out))) - return out.value - - -# Backend - - -def set_backend(backend_c_value: int, /) -> None: - """ - source: https://arrayfire.org/docs/group__unified__func__setbackend.htm#ga6fde820e8802776b7fc823504b37f1b4 - """ - safe_call(_backend.clib.af_set_backend(backend_c_value)) - return None - - -def get_backend_count() -> int: - """ - source: https://arrayfire.org/docs/group__unified__func__getbackendcount.htm#gad38c2dfedfdabfa264afa46d8664e9cd - """ - out = ctypes.c_int(0) - safe_call(_backend.clib.get().af_get_backend_count(ctypes.pointer(out))) - return out.value - - -def get_device_id(arr: AFArrayType, /) -> int: - """ - source: https://arrayfire.org/docs/group__unified__func__getdeviceid.htm#ga5d94b64dccd1c7cbc7a3a69fa64888c3 - """ - out = ctypes.c_int(0) - safe_call(_backend.clib.get().af_get_device_id(ctypes.pointer(out), arr)) - return out.value - - -def get_size_of(dtype: Dtype, /) -> int: - """ - source: https://arrayfire.org/docs/util_8h.htm#a8b72cffd10a92a7a2ee7f52dadda5216 - """ - out = ctypes.c_size_t(0) - safe_call(_backend.clib.get().af_get_size_of(ctypes.pointer(out), dtype.c_api_value)) - return out.value - - -def get_backend_id(arr: AFArrayType, /) -> int: - """ - source: https://arrayfire.org/docs/group__unified__func__getbackendid.htm#ga5fc39e209e1886cf250aec265c0d9079 - """ - out = ctypes.c_int(0) - safe_call(_backend.clib.get().af_get_backend_id(ctypes.pointer(out), arr)) - return out.value - - -# Cuda specific - - -def get_stream(index: int) -> int: - """ - source: https://arrayfire.org/docs/group__cuda__mat.htm#ga8323b850f80afe9878b099f647b0a7e5 - """ - out = AFArrayType.create_pointer() - safe_call(_backend.clib.get().afcu_get_stream(ctypes.pointer(out), index)) - return out.value - - -def get_native_id(index: int) -> int: - """ - source: https://arrayfire.org/docs/group__cuda__mat.htm#gaf38af1cbbf4be710cc8cbd95d20b24c4 - """ - out = ctypes.c_int(0) - safe_call(_backend.clib.get().afcu_get_native_id(ctypes.pointer(out), index)) - return out.value - - -def set_native_id(index: int) -> None: - """ - source: https://arrayfire.org/docs/group__cuda__mat.htm#ga966f4c6880e90ce91d9599c90c0db378 - """ - safe_call(_backend.clib.get().afcu_set_native_id(index)) - return None - - -def cublas_set_math_mode(mode: int) -> None: - """ - source: https://arrayfire.org/docs/group__cuda__mat.htm#gac23ea38f0bff77a0e12555f27f47aa4f - """ - safe_call(_backend.clib.get().afcu_cublasSetMathMode(mode)) - return None diff --git a/arrayfire/dtypes.py b/arrayfire/dtypes.py index 0462fcb..ac10fee 100644 --- a/arrayfire/dtypes.py +++ b/arrayfire/dtypes.py @@ -1,45 +1,38 @@ from __future__ import annotations -import ctypes -from dataclasses import dataclass -from typing import Type +from typing import TypeAlias -from arrayfire.backend._backend import is_arch_x86 +_pybool: TypeAlias = bool -CType = Type[ctypes._SimpleCData] -_python_bool = bool - - -@dataclass(frozen=True) -class Dtype: - name: str - typecode: str - c_type: CType - typename: str - c_api_value: int # Internal use only - - def __str__(self) -> str: - return self.name - - def __repr__(self) -> str: - return f"arrayfire.{self.name}(typecode<{self.typecode}>)" - - -# Specification required -int8 = Dtype("int8", "i8", ctypes.c_char, "int8", 4) # HACK int8 - Not Supported, b8? -int16 = Dtype("int16", "h", ctypes.c_short, "short int", 10) -int32 = Dtype("int32", "i", ctypes.c_int, "int", 5) -int64 = Dtype("int64", "l", ctypes.c_longlong, "long int", 8) -uint8 = Dtype("uint8", "B", ctypes.c_ubyte, "unsigned_char", 7) -uint16 = Dtype("uint16", "H", ctypes.c_ushort, "unsigned short int", 11) -uint32 = Dtype("uint32", "I", ctypes.c_uint, "unsigned int", 6) -uint64 = Dtype("uint64", "L", ctypes.c_ulonglong, "unsigned long int", 9) -float16 = Dtype("float16", "e", ctypes.c_uint16, "half", 12) -float32 = Dtype("float32", "f", ctypes.c_float, "float", 0) -float64 = Dtype("float64", "d", ctypes.c_double, "double", 2) -complex64 = Dtype("complex64", "F", ctypes.c_float * 2, "float complex", 1) # type: ignore[arg-type] -complex128 = Dtype("complex128", "D", ctypes.c_double * 2, "double complex", 3) # type: ignore[arg-type] -bool = Dtype("bool", "b", ctypes.c_bool, "bool", 4) +from arrayfire_wrapper import ( # noqa + Dtype, + b8, + bool, + c32, + c64, + complex32, + complex64, + f16, + f32, + f64, + float16, + float32, + float64, + int16, + int32, + int64, + s16, + s32, + s64, + u8, + u16, + u32, + u64, + uint8, + uint16, + uint32, + uint64, +) supported_dtypes = ( int16, @@ -53,64 +46,127 @@ def __repr__(self) -> str: float32, float64, complex64, - complex128, + complex32, bool, - int8, # BUG if place on top of the list + s16, + s32, + s64, + u8, + u16, + u32, + u64, + f16, + f32, + f64, + c32, + c64, + b8, ) - -def is_complex_dtype(dtype: Dtype) -> _python_bool: - return dtype in {complex64, complex128} +# CType = Type[ctypes._SimpleCData] -c_dim_t = ctypes.c_int if is_arch_x86() else ctypes.c_longlong -ShapeType = tuple[int, ...] +# @dataclass(frozen=True) +# class Dtype: +# name: str +# typecode: str +# c_type: CType +# typename: str +# c_api_value: int # Internal use only +# def __str__(self) -> str: +# return self.name -class CShape(tuple): - def __new__(cls, *args: int) -> CShape: - cls.original_shape = len(args) - return tuple.__new__(cls, args) +# def __repr__(self) -> str: +# return f"arrayfire.{self.name}(typecode<{self.typecode}>)" - def __init__(self, x1: int = 1, x2: int = 1, x3: int = 1, x4: int = 1) -> None: - self.x1 = x1 - self.x2 = x2 - self.x3 = x3 - self.x4 = x4 - def __repr__(self) -> str: - return f"{self.__class__.__name__}{self.x1, self.x2, self.x3, self.x4}" - - @property - def c_array(self): # type: ignore[no-untyped-def] - c_shape = c_dim_t * 4 # ctypes.c_int | ctypes.c_longlong * 4 - return c_shape(c_dim_t(self.x1), c_dim_t(self.x2), c_dim_t(self.x3), c_dim_t(self.x4)) - - -def to_str(c_str: ctypes.c_char_p) -> str: - return str(c_str.value.decode("utf-8")) # type: ignore[union-attr] - - -def implicit_dtype(number: int | float | _python_bool | complex, array_dtype: Dtype) -> Dtype: - if isinstance(number, _python_bool): +# Specification required +# int8 = Dtype("int8", "i8", ctypes.c_char, "int8", 4) # HACK int8 - Not Supported, b8? +# int16 = Dtype("int16", "h", ctypes.c_short, "short int", 10) +# int32 = Dtype("int32", "i", ctypes.c_int, "int", 5) +# int64 = Dtype("int64", "l", ctypes.c_longlong, "long int", 8) +# uint8 = Dtype("uint8", "B", ctypes.c_ubyte, "unsigned_char", 7) +# uint16 = Dtype("uint16", "H", ctypes.c_ushort, "unsigned short int", 11) +# uint32 = Dtype("uint32", "I", ctypes.c_uint, "unsigned int", 6) +# uint64 = Dtype("uint64", "L", ctypes.c_ulonglong, "unsigned long int", 9) +# float16 = Dtype("float16", "e", ctypes.c_uint16, "half", 12) +# float32 = Dtype("float32", "f", ctypes.c_float, "float", 0) +# float64 = Dtype("float64", "d", ctypes.c_double, "double", 2) +# complex64 = Dtype("complex64", "F", ctypes.c_float * 2, "float complex", 1) # type: ignore[arg-type] +# complex128 = Dtype("complex128", "D", ctypes.c_double * 2, "double complex", 3) # type: ignore[arg-type] +# bool = Dtype("bool", "b", ctypes.c_bool, "bool", 4) + +# supported_dtypes = ( +# int16, +# int32, +# int64, +# uint8, +# uint16, +# uint32, +# uint64, +# float16, +# float32, +# float64, +# complex64, +# complex128, +# bool, +# int8, # BUG if place on top of the list +# ) + + +def is_complex_dtype(dtype: Dtype) -> _pybool: + return dtype in {complex32, complex64} + + +# c_dim_t = ctypes.c_int if is_arch_x86() else ctypes.c_longlong +# ShapeType = tuple[int, ...] + + +# class CShape(tuple): +# def __new__(cls, *args: int) -> CShape: +# cls.original_shape = len(args) +# return tuple.__new__(cls, args) + +# def __init__(self, x1: int = 1, x2: int = 1, x3: int = 1, x4: int = 1) -> None: +# self.x1 = x1 +# self.x2 = x2 +# self.x3 = x3 +# self.x4 = x4 + +# def __repr__(self) -> str: +# return f"{self.__class__.__name__}{self.x1, self.x2, self.x3, self.x4}" + +# @property +# def c_array(self): # type: ignore[no-untyped-def] +# c_shape = c_dim_t * 4 # ctypes.c_int | ctypes.c_longlong * 4 +# return c_shape(c_dim_t(self.x1), c_dim_t(self.x2), c_dim_t(self.x3), c_dim_t(self.x4)) + + +# def to_str(c_str: ctypes.c_char_p) -> str: +# return str(c_str.value.decode("utf-8")) # type: ignore[union-attr] + + +def implicit_dtype(number: int | float | _pybool | complex, array_dtype: Dtype) -> Dtype: + if isinstance(number, _pybool): number_dtype = bool elif isinstance(number, int): number_dtype = int64 elif isinstance(number, float): number_dtype = float64 elif isinstance(number, complex): - number_dtype = complex128 + number_dtype = complex64 else: raise TypeError(f"{type(number)} is not supported and can not be converted to af.Dtype.") - if not (array_dtype == float32 or array_dtype == complex64): + if not (array_dtype == float32 or array_dtype == complex32): return number_dtype if number_dtype == float64: return float32 - if number_dtype == complex128: - return complex64 + if number_dtype == complex64: + return complex32 return number_dtype diff --git a/arrayfire/library/_backend_functions.py b/arrayfire/library/_backend_functions.py new file mode 100644 index 0000000..5d708a5 --- /dev/null +++ b/arrayfire/library/_backend_functions.py @@ -0,0 +1,305 @@ +# FIXME +# from __future__ import annotations + +# import warnings +# from enum import Enum +# from typing import TYPE_CHECKING + +# from arrayfire_wrapper import Backend, BackendType, get_backend +# from arrayfire_wrapper.lib import cublas_set_math_mode +# from arrayfire_wrapper.lib import get_backend_count as c_get_backend_count +# from arrayfire_wrapper.lib import get_backend_id as c_get_backend_id +# from arrayfire_wrapper.lib import get_device_id as c_get_device_id +# from arrayfire_wrapper.lib import get_native_id as c_get_native_id +# from arrayfire_wrapper.lib import get_size_of as c_get_size_of +# from arrayfire_wrapper.lib import get_stream as c_get_stream +# from arrayfire_wrapper.lib import set_backend as c_set_backend +# from arrayfire_wrapper.lib import set_native_id as c_set_native_id + +# if TYPE_CHECKING: +# from arrayfire import Array +# from arrayfire.dtypes import Dtype + + +# class CublasMathMode(Enum): +# default = 0 +# tensor_op = 1 + + +# def set_backend(backend_type: BackendType | str) -> None: +# """ +# Set a specific backend by backend_type name. + +# Parameters +# ---------- +# backend_type : BackendType | str +# Name of the backend type to set. + +# Raises +# ------ +# ValueError +# If the given backend_type name is not a valid name for backend backend_type. +# TypeError +# If the given backend_type is not a valid type for backend backend_type. +# RuntimeError +# If the given backend_type is already the active backend backend_type. +# RuntimeError +# If the given backend_type could not be set as new backend backend_type. +# """ +# backend = get_backend() +# current_active_backend_type = backend.backend_type + +# if isinstance(backend_type, str): +# if backend_type not in [d.name for d in BackendType]: +# raise ValueError(f"{backend_type} is not a valid name for backend backend_type.") +# backend_type = BackendType[backend_type] + +# if not isinstance(backend_type, BackendType): +# raise TypeError(f"{backend_type} is not a valid type for backend backend_type.") + +# if current_active_backend_type == backend_type: +# raise RuntimeError(f"{backend_type} is already the active backend backend_type.") + +# if backend.backend_type == BackendType.unified: +# c_set_backend(backend_type.value) + +# # NOTE keep in mind that this operation works in-place +# # FIXME should not access private API +# backend._load_backend_lib(backend_type) + +# if current_active_backend_type == backend.backend_type: +# raise RuntimeError(f"Could not set {backend_type} as new backend backend_type. Consider checking logs.") + + +# def get_array_backend_name(array: Array) -> str: +# """ +# Get the name of the backend on which the Array is located. + +# Parameters +# ---------- +# array : Array +# The Array to get the backend name of. + +# Returns +# ------- +# value : str +# Name of the backend on which the Array is located. +# """ + +# id_ = c_get_backend_id(array.arr) +# return BackendType(id_).name + + +# def get_backend_id(array: Array) -> str: +# warnings.warn("Was renamed. Now get_array_backend_name() in main repo.", DeprecationWarning) +# return get_array_backend_name(array) + + +# def get_backend_count() -> int: +# """ +# Get a number of available backends. + +# Returns +# ------- + +# value : int +# Number of available backends. +# """ + +# return c_get_backend_count() + + +# def get_active_backend() -> Backend: +# """ +# Get the current active backend. + +# value : Backend +# Current active backend. +# """ + +# # TODO do not deprecate +# warnings.warn("A user has access explicitly only to the active backend.", DeprecationWarning) +# return get_backend() + + +# def get_available_backends() -> Backend: +# """ +# Get the list of available backends. + +# Returns +# ------- +# value : Backend +# Current active backend. +# """ + +# # TODO do not deprecate +# warnings.warn( +# "A user has access explicitly only to the active backend. Thus returning only active backend.", +# DeprecationWarning, +# ) +# return get_active_backend() + + +# def get_array_device_id(array: Array) -> int: +# """ +# Get the id of the device on which the Array was created. + +# Parameters +# ---------- +# array : Array +# The Array to get the device id of. + +# Returns +# ------- +# value : int +# The id of the device on which the Array was created. +# """ + +# return c_get_device_id(array.arr) + + +# def get_device_id(array: Array) -> int: +# warnings.warn("Was renamed due to unintuitive function name. Now get_array_device_id().", DeprecationWarning) +# return get_array_device_id(array) + + +# def get_dtype_size(dtype: Dtype) -> int: +# """ +# Get the size of the type represented by Dtype. + +# Parameters +# ---------- +# dtype : Dtype +# The type to get the size of. + +# Returns +# ------- +# value : int +# The size of the type in bytes. +# """ + +# return c_get_size_of(dtype) + + +# def get_size_of(dtype: Dtype) -> int: +# warnings.warn("Was renamed due to unintuitive function name. Now get_dtype_size().", DeprecationWarning) +# return get_dtype_size(dtype) + + +# # Previously module arrayfire.cuda + + +# def _check_if_cuda_used() -> None: +# backend = get_backend() +# if backend.backend_type != BackendType.cuda: +# raise RuntimeError( +# f"Can not get the CUDA stream id because the other backend is in use: {backend.backend_type}." +# ) + + +# def get_stream(index: int) -> int: +# warnings.warn("Was renamed due to unintuitive function name. Now get_cuda_stream().", DeprecationWarning) +# return get_cuda_stream(index) + + +# def get_cuda_stream(index: int) -> int: +# """ +# Get the CUDA stream used for the device id by ArrayFire. + +# Parameters +# ---------- +# idx : int +# Specifies the index of the device. + +# Returns +# ------- +# value : int +# Denoting the stream id. + +# Raises +# ------ +# RuntimeError +# If the current backend type is not CUDA. +# """ +# _check_if_cuda_used() + +# return c_get_stream(index) + + +# def get_native_id(index: int) -> int: +# warnings.warn("Was renamed due to unintuitive function name. Now get_native_cuda_id().", DeprecationWarning) +# return get_native_cuda_id(index) + + +# def get_native_cuda_id(index: int) -> int: +# """ +# Get native (unsorted) CUDA device id. + +# Parameters +# ---------- +# idx : int +# Specifies the (sorted) index of the device. + +# Returns +# ------- +# value : int +# Denoting the native cuda id. + +# Raises +# ------ +# RuntimeError +# If the current backend type is not CUDA. +# """ +# _check_if_cuda_used() + +# return c_get_native_id(index) + + +# def set_native_id(index: int) -> None: +# warnings.warn("Was renamed due to unintuitive function name. Now get_native_cuda_id().", DeprecationWarning) +# return set_native_cuda_id(index) + + +# def set_native_cuda_id(index: int) -> None: +# """ +# Set native (unsorted) CUDA device id. + +# Parameters +# ---------- +# idx : int +# Specifies the (unsorted) native index of the device. + +# Raises +# ------ +# RuntimeError +# If the current backend type is not CUDA. +# """ +# _check_if_cuda_used() + +# return c_set_native_id(index) + + +# def set_cublas_mode(mode: CublasMathMode | int = CublasMathMode.default) -> None: +# """ +# Set cuBLAS math mode for CUDA backend. It enables the Tensor Core usage if available on CUDA backend GPUs. + +# Parameters +# ---------- +# mode : CublasMathMode | int +# Specify the mode available within CublasMathMode enum. + +# Raises +# ------ +# ValueError +# If the given math mode int value is not a valid value for cuBLAS math mode. +# RuntimeError +# If the current backend type is not CUDA. +# """ +# if isinstance(mode, int): +# if mode not in [m.value for m in CublasMathMode]: +# raise ValueError(f"{mode} is not supported as cublas math mode.") +# mode = CublasMathMode(mode) + +# _check_if_cuda_used() + +# return cublas_set_math_mode(mode.value) diff --git a/arrayfire/library/broadcast.py b/arrayfire/library/broadcast.py deleted file mode 100644 index 65b031f..0000000 --- a/arrayfire/library/broadcast.py +++ /dev/null @@ -1,91 +0,0 @@ -from collections.abc import Callable -from typing import Any - - -class Bcast: - def __init__(self) -> None: - self._flag: bool = False - - def get(self) -> bool: - return self._flag - - def set(self, flag: bool) -> None: - self._flag = flag - - def toggle(self) -> None: - self._flag ^= True - - -bcast_var: Bcast = Bcast() - - -def broadcast(func: Callable[..., Any], *args: Any) -> Any: - """ - Function to perform broadcast operations. - - This function can be used directly or as an annotation in the following manner. - - Example - ------- - - Using broadcast as an annotation - - >>> import arrayfire as af - >>> @af.broadcast - ... def add(a, b): - ... return a + b - ... - >>> a = af.randu(2,3) - >>> b = af.randu(2,1) # b is a different size - >>> # Trying to add arrays of different sizes raises an exceptions - >>> c = add(a, b) # This call does not raise an exception because of the annotation - >>> af.display(a) - [2 3 1 1] - 0.4107 0.9518 0.4198 - 0.8224 0.1794 0.0081 - - >>> af.display(b) - [2 1 1 1] - 0.7269 - 0.7104 - - >>> af.display(c) - [2 3 1 1] - 1.1377 1.6787 1.1467 - 1.5328 0.8898 0.7185 - - Using broadcast as function - - >>> import arrayfire as af - >>> add = lambda a,b: a + b - >>> a = af.randu(2,3) - >>> b = af.randu(2,1) # b is a different size - >>> # Trying to add arrays of different sizes raises an exceptions - >>> c = af.broadcast(add, a, b) # This call does not raise an exception - >>> af.display(a) - [2 3 1 1] - 0.4107 0.9518 0.4198 - 0.8224 0.1794 0.0081 - - >>> af.display(b) - [2 1 1 1] - 0.7269 - 0.7104 - - >>> af.display(c) - [2 3 1 1] - 1.1377 1.6787 1.1467 - 1.5328 0.8898 0.7185 - - """ - - def wrapper(*func_args: Any) -> Any: - bcast_var.toggle() - res = func(*func_args) - bcast_var.toggle() - return res - - if len(args) == 0: - return wrapper() - else: - return wrapper(*args) diff --git a/arrayfire/library/constant_array.py b/arrayfire/library/constant_array.py new file mode 100644 index 0000000..e50d563 --- /dev/null +++ b/arrayfire/library/constant_array.py @@ -0,0 +1,20 @@ +from arrayfire_wrapper import AFArray +from arrayfire_wrapper.lib import constant, constant_complex, constant_long, constant_ulong + +from arrayfire.dtypes import Dtype, complex32, implicit_dtype, int64, is_complex_dtype, uint64 + + +def create_constant_array(number: int | float | complex, shape: tuple[int, ...], dtype: Dtype, /) -> AFArray: + if not dtype: + dtype = implicit_dtype(number, dtype) + + if isinstance(number, complex): + return constant_complex(number, shape, dtype if is_complex_dtype(dtype) else complex32) + + if dtype == int64: + return constant_long(number, shape, dtype) + + if dtype == uint64: + return constant_ulong(number, shape, dtype) + + return constant(number, shape, dtype) diff --git a/arrayfire/library/constants.py b/arrayfire/library/constants.py index 0dcada0..2d6b717 100644 --- a/arrayfire/library/constants.py +++ b/arrayfire/library/constants.py @@ -1,6 +1,7 @@ +__all__ = ["pi"] + import math -from arrayfire.backend._clib_wrapper._error_handler import constant -from arrayfire.dtypes import float64 +import arrayfire_wrapper as afw -pi = constant(math.pi, (1,), float64) +pi = afw.lib.constant(math.pi, (1,), afw.float64) diff --git a/arrayfire/library/data.py b/arrayfire/library/data.py index b8080a0..d636e53 100644 --- a/arrayfire/library/data.py +++ b/arrayfire/library/data.py @@ -1,9 +1,11 @@ from typing import cast +import arrayfire_wrapper.lib as wrapper + from arrayfire import Array from arrayfire.array_object import afarray_as_array -from arrayfire.backend import _clib_wrapper as wrapper from arrayfire.dtypes import Dtype, float32 +from arrayfire.library.constant_array import create_constant_array _pyrange = range @@ -39,7 +41,7 @@ def constant(scalar: int | float | complex, shape: tuple[int, ...] = (1,), dtype - If shape is (x1, x2, x3), the output is a 3D array of size (x1, x2, x3). - If shape is (x1, x2, x3, x4), the output is a 4D array of size (x1, x2, x3, x4). """ - result = wrapper.create_constant_array(scalar, shape, dtype) + result = create_constant_array(scalar, shape, dtype) return cast(Array, result) # HACK actually it return AFArrayType, but decorator makes it an ArrayFire Array. @@ -101,7 +103,7 @@ def range(shape: tuple[int, ...], axis: int = 0, dtype: Dtype = float32) -> Arra f"Can not calculate along {axis} dimension. The resulting Array is set to has {len(shape)} dimensions." ) - result = wrapper.af_range(shape, axis, dtype) + result = wrapper.range(shape, axis, dtype) return cast(Array, result) # HACK actually it return AFArrayType, but decorator makes it an ArrayFire Array. diff --git a/arrayfire/library/device.py b/arrayfire/library/device.py index ab5d96d..ece3a94 100644 --- a/arrayfire/library/device.py +++ b/arrayfire/library/device.py @@ -1,6 +1,6 @@ import enum -from arrayfire.backend import _clib_wrapper as wrapper +import arrayfire_wrapper.lib as wrapper class PointerSource(enum.Enum): @@ -18,6 +18,3 @@ def get_device() -> int: # FIXME def sync(device_id: int) -> None: # FIXME return wrapper.sync(device_id) - - -supported_devices = [] diff --git a/arrayfire/library/old_operators.py b/arrayfire/library/old_operators.py deleted file mode 100644 index 9924482..0000000 --- a/arrayfire/library/old_operators.py +++ /dev/null @@ -1,310 +0,0 @@ -from collections.abc import Callable - -from arrayfire import Array -from arrayfire.backend import _clib_wrapper as wrapper - - -class return_copy: - # TODO merge with process_c_function in array_object - def __init__(self, func: Callable) -> None: - self.func = func - - def __call__(self, x1: Array, x2: Array) -> Array: - out = Array() - out.arr = self.func(x1.arr, x2.arr) - return out - - -@return_copy -def abs(x: Array, /) -> Array: - return wrapper.abs(x) # type: ignore[arg-type, return-value] - - -@return_copy -def acos(x: Array, /) -> Array: - return wrapper.acos(x) # type: ignore[arg-type, return-value] - - -@return_copy -def acosh(x: Array, /) -> Array: - return wrapper.acosh(x) # type: ignore[arg-type, return-value] - - -@return_copy -def add(x1: Array, x2: Array, /) -> Array: - return wrapper.add(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def asin(x: Array, /) -> Array: - return wrapper.asin(x) # type: ignore[arg-type, return-value] - - -@return_copy -def asinh(x: Array, /) -> Array: - return wrapper.asinh(x) # type: ignore[arg-type, return-value] - - -@return_copy -def atan(x: Array, /) -> Array: - return wrapper.atan(x) # type: ignore[arg-type, return-value] - - -@return_copy -def atan2(x1: Array, x2: Array, /) -> Array: - return wrapper.atan2(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def atanh(x: Array, /) -> Array: - return wrapper.atanh(x) # type: ignore[arg-type, return-value] - - -@return_copy -def bitwise_and(x1: Array, x2: Array, /) -> Array: - return wrapper.bitand(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def bitwise_left_shift(x1: Array, x2: Array, /) -> Array: - return wrapper.bitshiftl(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def bitwise_invert(x: Array, /) -> Array: - return wrapper.bitnot(x) # type: ignore[arg-type, return-value] - - -@return_copy -def bitwise_or(x1: Array, x2: Array, /) -> Array: - return wrapper.bitor(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def bitwise_right_shift(x1: Array, x2: Array, /) -> Array: - return wrapper.bitshiftr(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def bitwise_xor(x1: Array, x2: Array, /) -> Array: - return wrapper.bitxor(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def ceil(x: Array, /) -> Array: - return wrapper.ceil(x) # type: ignore[arg-type, return-value] - - -@return_copy -def conj(x: Array, /) -> Array: - return wrapper.conjg(x) # type: ignore[arg-type, return-value] - - -@return_copy -def cos(x: Array, /) -> Array: - return wrapper.cos(x) # type: ignore[arg-type, return-value] - - -@return_copy -def cosh(x: Array, /) -> Array: - return wrapper.cosh(x) # type: ignore[arg-type, return-value] - - -@return_copy -def divide(x1: Array, x2: Array, /) -> Array: - return wrapper.div(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def equal(x1: Array, x2: Array, /) -> Array: - return wrapper.eq(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def exp(x: Array, /) -> Array: - return wrapper.exp(x) # type: ignore[arg-type, return-value] - - -@return_copy -def expm1(x: Array, /) -> Array: - return wrapper.expm1(x) # type: ignore[arg-type, return-value] - - -@return_copy -def floor(x: Array, /) -> Array: - return wrapper.floor(x) # type: ignore[arg-type, return-value] - - -@return_copy -def floor_divide(x1: Array, x2: Array, /) -> Array: - return NotImplemented - - -@return_copy -def greater(x1: Array, x2: Array, /) -> Array: - return wrapper.gt(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def greater_equal(x1: Array, x2: Array, /) -> Array: - return wrapper.ge(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def imag(x: Array, /) -> Array: - return wrapper.imag(x) # type: ignore[arg-type, return-value] - - -@return_copy -def isfinite(x: Array, /) -> Array: - return NotImplemented - - -@return_copy -def isinf(x: Array, /) -> Array: - return wrapper.isinf(x) # type: ignore[arg-type, return-value] - - -@return_copy -def isnan(x: Array, /) -> Array: - return wrapper.isnan(x) # type: ignore[arg-type, return-value] - - -@return_copy -def less(x1: Array, x2: Array, /) -> Array: - return wrapper.le(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def less_equal(x1: Array, x2: Array, /) -> Array: - return wrapper.lt(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def log(x: Array, /) -> Array: - return wrapper.log(x) # type: ignore[arg-type, return-value] - - -@return_copy -def log1p(x: Array, /) -> Array: - return wrapper.log1p(x) # type: ignore[arg-type, return-value] - - -@return_copy -def log2(x: Array, /) -> Array: - return wrapper.log2(x) # type: ignore[arg-type, return-value] - - -@return_copy -def log10(x: Array, /) -> Array: - return wrapper.log10(x) # type: ignore[arg-type, return-value] - - -@return_copy -def logaddexp(x1: Array, x2: Array, /) -> Array: - return NotImplemented - - -@return_copy -def logical_and(x1: Array, x2: Array, /) -> Array: - return wrapper.land(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def logical_not(x: Array, /) -> Array: - return wrapper.lnot(x) # type: ignore[arg-type, return-value] - - -@return_copy -def logical_or(x1: Array, x2: Array, /) -> Array: - return wrapper.lor(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def logical_xor(x1: Array, x2: Array, /) -> Array: - return NotImplemented - - -@return_copy -def multiply(x1: Array, x2: Array, /) -> Array: - return wrapper.mul(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def negative(x: Array, /) -> Array: - return wrapper.sub(0, x) # type: ignore[arg-type, return-value] - - -@return_copy -def not_equal(x1: Array, x2: Array, /) -> Array: - return wrapper.neq(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def positive(x: Array, /) -> Array: - return x # type: ignore[arg-type, return-value] - - -@return_copy -def pow(x1: Array, x2: Array, /) -> Array: - return wrapper.pow(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def real(x: Array, /) -> Array: - return wrapper.real(x) # type: ignore[arg-type, return-value] - - -@return_copy -def remainder(x1: Array, x2: Array, /) -> Array: - return wrapper.rem(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def round(x: Array, /) -> Array: - return wrapper.round(x) # type: ignore[arg-type, return-value] - - -@return_copy -def sign(x: Array, /) -> Array: - return wrapper.sign(x) # type: ignore[arg-type, return-value] - - -@return_copy -def sin(x: Array, /) -> Array: - return wrapper.sin(x) # type: ignore[arg-type, return-value] - - -@return_copy -def sinh(x: Array, /) -> Array: - return wrapper.sinh(x) # type: ignore[arg-type, return-value] - - -@return_copy -def square(x: Array, /) -> Array: - return wrapper.pow(x, 2) # type: ignore[arg-type, return-value] - - -@return_copy -def sqrt(x: Array, /) -> Array: - return wrapper.sqrt(x) # type: ignore[arg-type, return-value] - - -@return_copy -def subtract(x1: Array, x2: Array, /) -> Array: - return wrapper.sub(x1, x2) # type: ignore[arg-type, return-value] - - -@return_copy -def tan(x: Array, /) -> Array: - return wrapper.tan(x) # type: ignore[arg-type, return-value] - - -@return_copy -def tanh(x: Array, /) -> Array: - return wrapper.tanh(x) # type: ignore[arg-type, return-value] - - -@return_copy -def trunc(x: Array, /) -> Array: - return wrapper.trunc(x) # type: ignore[arg-type, return-value] diff --git a/arrayfire/library/operators.py b/arrayfire/library/operators.py index c6685e1..6e6eccf 100755 --- a/arrayfire/library/operators.py +++ b/arrayfire/library/operators.py @@ -2,9 +2,12 @@ from typing import cast +import arrayfire_wrapper.lib as wrapper + from arrayfire import Array from arrayfire.array_object import afarray_as_array -from arrayfire.backend import _clib_wrapper as wrapper + +# from arrayfire._backup_backend import _clib_wrapper as wrapper from arrayfire.dtypes import is_complex_dtype @@ -64,7 +67,7 @@ def mod(x1: int | float | Array, x2: int | float | Array, /) -> Array: def pow(x1: int | float | Array, x2: int | float | Array, /) -> Array: _check_operands_fit_requirements(x1, x2) - return wrapper.pow(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + return wrapper.pow(x1.arr, x2.arr) @afarray_as_array @@ -321,7 +324,7 @@ def pow2(x: Array, /) -> Array: @afarray_as_array def sigmoid(x: Array, /) -> Array: _check_array_values_not_complex(x) - return wrapper.sigmoid(x.arr) # type: ignore[arg-type, return-value] + return wrapper.sigmoid(x.arr) @afarray_as_array @@ -427,18 +430,18 @@ def isnan(x: Array, /) -> Array: @afarray_as_array -def land(x1: Array, x2: Array, /) -> Array: - return wrapper.land(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def and_(x1: Array, x2: Array, /) -> Array: + return wrapper.and_(x1.arr, x2.arr) # type: ignore[arg-type, return-value] @afarray_as_array -def lor(x1: Array, x2: Array, /) -> Array: - return wrapper.lor(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def or_(x1: Array, x2: Array, /) -> Array: + return wrapper.or_(x1.arr, x2.arr) # type: ignore[arg-type, return-value] @afarray_as_array -def lnot(x: Array, /) -> Array: - return wrapper.lnot(x.arr) # type: ignore[arg-type, return-value] +def not_(x: Array, /) -> Array: + return wrapper.not_(x.arr) # type: ignore[arg-type, return-value] def _check_operands_fit_requirements(x1: int | float | Array, x2: int | float | Array) -> None: diff --git a/arrayfire/library/random.py b/arrayfire/library/random.py index 35fd66a..88d6353 100644 --- a/arrayfire/library/random.py +++ b/arrayfire/library/random.py @@ -3,9 +3,10 @@ from enum import Enum from typing import cast +import arrayfire_wrapper.lib as wrapper + from arrayfire import Array from arrayfire.array_object import afarray_as_array -from arrayfire.backend import _clib_wrapper as wrapper from arrayfire.dtypes import Dtype, float32 @@ -99,25 +100,25 @@ def get_seed(self) -> int: """ return wrapper.random_engine_get_seed(self._engine) - def get_engine(self) -> wrapper.AFRandomEngine: + def get_engine(self) -> wrapper.AFRandomEngineHandle: """ Get the ArrayFire random engine handle. Returns ------- - wrapper.AFRandomEngine + wrapper.AFRandomEngineHandle The ArrayFire random engine handle associated with this RandomEngine instance. """ return self._engine @classmethod - def from_engine(cls, engine: wrapper.AFRandomEngine) -> RandomEngine: + def from_engine(cls, engine: wrapper.AFRandomEngineHandle) -> RandomEngine: """ Create a RandomEngine instance from an existing RandomEngine handle. Parameters ---------- - engine : wrapper.AFRandomEngine + engine : wrapper.AFRandomEngineHandle The existing RandomEngine handle. Returns diff --git a/arrayfire/library/utils.py b/arrayfire/library/utils.py index b1e08e5..d26e8f1 100644 --- a/arrayfire/library/utils.py +++ b/arrayfire/library/utils.py @@ -1,7 +1,9 @@ from typing import cast as typing_cast +import arrayfire_wrapper.lib as wrapper +from arrayfire_wrapper import AFArray + from arrayfire.array_object import Array, afarray_as_array -from arrayfire.backend import _clib_wrapper as wrapper from arrayfire.dtypes import Dtype @@ -39,6 +41,10 @@ def cast(array: Array, dtype: Dtype, /) -> Array: return typing_cast(Array, wrapper.cast(array.arr, dtype)) +# def array_as_str(arr: AFArray) -> str: +# return wrapper.array_to_string("", arr, 4, True) + + # def timeit(af_func, *args): # """ # Function to time arrayfire functions. diff --git a/arrayfire/library/vector_algorithms/reduction_operations.py b/arrayfire/library/vector_algorithms/reduction_operations.py index aab2cf6..196604b 100644 --- a/arrayfire/library/vector_algorithms/reduction_operations.py +++ b/arrayfire/library/vector_algorithms/reduction_operations.py @@ -1,9 +1,10 @@ from collections.abc import Callable from typing import Any, cast +from arrayfire_wrapper import lib as wrapper + from arrayfire import Array from arrayfire.array_object import afarray_as_array -from arrayfire.backend import _clib_wrapper as wrapper @afarray_as_array diff --git a/requirements.txt b/requirements.txt index e69de29..87151f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1 @@ +git+https://github.com/roaffix/arrayfire-python-wrapper # TODO fix versioning dependency diff --git a/tests/test_dtypes.py b/tests/test_dtypes.py index d7ad295..ef50efa 100755 --- a/tests/test_dtypes.py +++ b/tests/test_dtypes.py @@ -4,7 +4,18 @@ from arrayfire.dtypes import Dtype from arrayfire.dtypes import bool as af_bool -from arrayfire.dtypes import complex128, float32, float64, implicit_dtype, int8, int32, int64, str_to_dtype, uint16 +from arrayfire.dtypes import ( + complex64, + float32, + float64, + implicit_dtype, + int16, + int32, + int64, + s16, + str_to_dtype, + uint16, +) def test_dtype_str_representation() -> None: @@ -22,7 +33,7 @@ def test_dtype_equality() -> None: def test_dtype_inequality() -> None: - assert float32 != int8 + assert float32 != int16 @pytest.mark.parametrize( @@ -32,7 +43,7 @@ def test_dtype_inequality() -> None: (1.0, float64, float64), (1.0, float32, float32), (True, float32, af_bool), - (1 + 2j, complex128, complex128), + (1 + 2j, complex64, complex64), ], ) def test_implicit_dtype(number: int | float | bool | complex, array_dtype: Dtype, expected_dtype: Dtype) -> None: @@ -58,7 +69,7 @@ def test_implicit_dtype_raises_error_invalid_combination() -> None: @pytest.mark.parametrize( "value,expected_dtype", [ - ("i8", int8), + ("short int", s16), ("int", int32), ("uint16", uint16), ("float", float32), diff --git a/tests/test_random.py b/tests/test_random.py index 83f52a6..6bbad59 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -1,7 +1,7 @@ import pytest +from arrayfire_wrapper.lib import create_random_engine from arrayfire import Array -from arrayfire.backend import _clib_wrapper as wrapper from arrayfire.library import random from arrayfire.library.random import RandomEngine, RandomEngineType @@ -21,7 +21,7 @@ def test_random_engine_creation() -> None: def test_random_engine_from_handle() -> None: # Test creating a random engine from an existing handle - handle = wrapper.create_random_engine(RandomEngineType.MERSENNE.value, 1232) + handle = create_random_engine(RandomEngineType.MERSENNE.value, 1232) engine = RandomEngine.from_engine(handle) assert engine.get_type() == RandomEngineType.MERSENNE assert engine.get_seed() == 1232 From 0fef575ebfa3655cef4b8ba63ddd8400f12c56d4 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 23 Sep 2023 01:16:54 +0300 Subject: [PATCH 03/16] Fix help() --- arrayfire/array_object.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arrayfire/array_object.py b/arrayfire/array_object.py index bbc87ae..e92557f 100755 --- a/arrayfire/array_object.py +++ b/arrayfire/array_object.py @@ -2,6 +2,7 @@ import array as _pyarray from collections.abc import Callable +from functools import wraps from typing import TYPE_CHECKING, Any, ParamSpec, cast import arrayfire_wrapper.lib as wrapper @@ -35,6 +36,7 @@ def afarray_as_array(func: Callable[P, Array]) -> Callable[P, Array]: A decorated function that returns an ArrayFire Array. """ + @wraps(func) def decorated(*args: P.args, **kwargs: P.kwargs) -> Array: out = Array() result = func(*args, **kwargs) From b208586f1ee59c42e2da719a7a4e9f7b28e48ff2 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 23 Sep 2023 03:47:23 +0300 Subject: [PATCH 04/16] Minor fixes --- arrayfire/library/operators.py | 15 +++------------ arrayfire/library/utils.py | 5 ----- .../test_reduction_operations.py | 5 ----- 3 files changed, 3 insertions(+), 22 deletions(-) diff --git a/arrayfire/library/operators.py b/arrayfire/library/operators.py index 6e6eccf..136cb96 100755 --- a/arrayfire/library/operators.py +++ b/arrayfire/library/operators.py @@ -130,18 +130,9 @@ def neq(x1: Array, x2: Array, /) -> Array: return wrapper.neq(x1.arr, x2.arr) # type: ignore[arg-type, return-value] -# @afarray_as_array -# def clamp(x: Array, /, lo: float, hi: float) -> Array: -# return NotImplemented - - -# # """ -# # source: https://arrayfire.org/docs/group__arith__func__clamp.htm#gac4e785c5c877c7905e56f44ef0cb5e61 -# # """ -# # # TODO: check if lo and hi are of type float. Can be ArrayFire array as well -# # out = AFArrayType.create_pointer() -# # safe_call(_backend.clib.af_clamp(ctypes.pointer(out), arr, lo, hi)) -# # return out +@afarray_as_array +def clamp(x: Array, /, lo: float, hi: float) -> Array: + return NotImplemented @afarray_as_array diff --git a/arrayfire/library/utils.py b/arrayfire/library/utils.py index d26e8f1..413c7d6 100644 --- a/arrayfire/library/utils.py +++ b/arrayfire/library/utils.py @@ -1,7 +1,6 @@ from typing import cast as typing_cast import arrayfire_wrapper.lib as wrapper -from arrayfire_wrapper import AFArray from arrayfire.array_object import Array, afarray_as_array from arrayfire.dtypes import Dtype @@ -41,10 +40,6 @@ def cast(array: Array, dtype: Dtype, /) -> Array: return typing_cast(Array, wrapper.cast(array.arr, dtype)) -# def array_as_str(arr: AFArray) -> str: -# return wrapper.array_to_string("", arr, 4, True) - - # def timeit(af_func, *args): # """ # Function to time arrayfire functions. diff --git a/tests/vector_algorithms/test_reduction_operations.py b/tests/vector_algorithms/test_reduction_operations.py index 899c3a4..f16b831 100644 --- a/tests/vector_algorithms/test_reduction_operations.py +++ b/tests/vector_algorithms/test_reduction_operations.py @@ -1,14 +1,9 @@ -from typing import TYPE_CHECKING - import pytest from arrayfire import Array from arrayfire.library import data from arrayfire.library import vector_algorithms as va -# if TYPE_CHECKING: -# from arrayfire import Array - @pytest.fixture def true_array() -> Array: From 46cdc97f0fa227a288750a29d4809610b8dc882d Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 30 Jan 2024 04:12:22 +0200 Subject: [PATCH 05/16] More changes --- arrayfire/array_object.py | 86 ++++++++++------- arrayfire/library/computer_vision.py | 85 +++++++++++++++++ arrayfire/library/constants.py | 14 ++- arrayfire/library/create_and_modify_array.py | 49 ++++++++++ arrayfire/library/operators.py | 7 +- .../library/signal_processing/__init__.py | 0 arrayfire/library/signal_processing/fft.py | 1 + .../inclusive_scan_operations.py | 94 +++++++++++++++++++ .../vector_algorithms/reduction_operations.py | 47 +++++++++- .../test_inclusive_scan_operations.py | 32 +++++++ 10 files changed, 371 insertions(+), 44 deletions(-) create mode 100644 arrayfire/library/computer_vision.py create mode 100644 arrayfire/library/create_and_modify_array.py create mode 100644 arrayfire/library/signal_processing/__init__.py create mode 100644 arrayfire/library/signal_processing/fft.py create mode 100644 arrayfire/library/vector_algorithms/inclusive_scan_operations.py create mode 100644 tests/vector_algorithms/test_inclusive_scan_operations.py diff --git a/arrayfire/array_object.py b/arrayfire/array_object.py index e92557f..c9705e2 100755 --- a/arrayfire/array_object.py +++ b/arrayfire/array_object.py @@ -38,10 +38,8 @@ def afarray_as_array(func: Callable[P, Array]) -> Callable[P, Array]: @wraps(func) def decorated(*args: P.args, **kwargs: P.kwargs) -> Array: - out = Array() result = func(*args, **kwargs) - out.arr = result # type: ignore[assignment] - return out + return Array.from_afarray(result) return decorated @@ -56,7 +54,7 @@ def __init__( offset: CType | None = None, strides: tuple[int, ...] | None = None, ) -> None: - self.arr = AFArray.create_null_pointer() + self._arr = AFArray.create_null_pointer() _no_initial_dtype = False # HACK, FIXME if isinstance(dtype, str): @@ -68,14 +66,14 @@ def __init__( if obj is None: if not shape: # shape is None or empty tuple - self.arr = wrapper.create_handle((), dtype) + self._arr = wrapper.create_handle((), dtype) return - self.arr = wrapper.create_handle(shape, dtype) + self._arr = wrapper.create_handle(shape, dtype) return if isinstance(obj, Array): - self.arr = wrapper.retain_array(obj.arr) + self._arr = wrapper.retain_array(obj.arr) return if isinstance(obj, _pyarray.array): @@ -120,13 +118,13 @@ def __init__( if not (offset or strides): if not to_device: - self.arr = wrapper.create_array(shape, dtype, _array_buffer) + self._arr = wrapper.create_array(shape, dtype, _array_buffer) return - self.arr = wrapper.device_array(shape, dtype, _array_buffer) + self._arr = wrapper.device_array(shape, dtype, _array_buffer) return - self.arr = wrapper.create_strided_array( + self._arr = wrapper.create_strided_array( shape, dtype, _array_buffer, offset, strides, PointerSource.device # type: ignore[arg-type] ) @@ -328,10 +326,7 @@ def __invert__(self) -> Array: out : Array An array containing the element-wise results. The returned array must have the same data type as self. """ - # FIXME - out = Array() - out.arr = wrapper.bitnot(self.arr) - return out + return Array.from_afarray(wrapper.bitnot(self._arr)) def __and__(self, other: int | bool | Array, /) -> Array: """ @@ -768,7 +763,7 @@ def __getitem__(self, key: IndexKey, /) -> Array: return out # HACK known issue - out.arr = wrapper.index_gen(self.arr, ndims, wrapper.get_indices(key)) # type: ignore[arg-type] + out._arr = wrapper.index_gen(self._arr, ndims, wrapper.get_indices(key)) # type: ignore[arg-type] return out def __index__(self) -> int: @@ -785,7 +780,7 @@ def __len__(self) -> int: def __setitem__(self, key: IndexKey, value: int | float | bool | Array, /) -> None: ndims = self.ndim - is_array_with_bool = isinstance(key, Array) and type(key) == afbool + is_array_with_bool = isinstance(key, Array) and type(key) is afbool if is_array_with_bool: ndims = 1 @@ -807,12 +802,12 @@ def __setitem__(self, key: IndexKey, value: int | float | bool | Array, /) -> No del_other = False indices = wrapper.get_indices(key) # type: ignore[arg-type] # FIXME - out = wrapper.assign_gen(self.arr, other_arr, ndims, indices) + out = wrapper.assign_gen(self._arr, other_arr, ndims, indices) - wrapper.release_array(self.arr) + wrapper.release_array(self._arr) if del_other: wrapper.release_array(other_arr) - self.arr = out + self._arr = out def __str__(self) -> str: # TODO change the look of array str. E.g., like np.array @@ -826,11 +821,11 @@ def __repr__(self) -> str: return _array_as_str(self) def __del__(self) -> None: - if not self.arr.value: + if not self._arr.value: return - wrapper.release_array(self.arr) - self.arr.value = 0 + wrapper.release_array(self._arr) + self._arr.value = 0 def to_device(self, device: Any, /, *, stream: int | Any = None) -> Array: # TODO implementation and change device type from Any to Device @@ -848,7 +843,7 @@ def dtype(self) -> Dtype: out : Dtype Array data type. """ - return c_api_value_to_dtype(wrapper.get_type(self.arr)) + return c_api_value_to_dtype(wrapper.get_type(self._arr)) @property def device(self) -> Any: @@ -876,12 +871,12 @@ def T(self) -> Array: raise TypeError(f"Array should be at least 2-dimensional. Got {self.ndim}-dimensional array") # TODO add check if out.dtype == self.dtype - return cast(Array, wrapper.transpose(self.arr, False)) + return cast(Array, wrapper.transpose(self._arr, False)) @property @afarray_as_array def H(self) -> Array: - return cast(Array, wrapper.transpose(self.arr, True)) + return cast(Array, wrapper.transpose(self._arr, True)) @property def size(self) -> int: @@ -898,7 +893,7 @@ def size(self) -> int: - This must equal the product of the array's dimensions. """ # NOTE previously - elements() - return wrapper.get_elements(self.arr) + return wrapper.get_elements(self._arr) @property def ndim(self) -> int: @@ -908,7 +903,7 @@ def ndim(self) -> int: int Number of array dimensions (axes). """ - return wrapper.get_numdims(self.arr) + return wrapper.get_numdims(self._arr) @property def shape(self) -> tuple[int, ...]: @@ -921,7 +916,7 @@ def shape(self) -> tuple[int, ...]: Array dimensions. """ # NOTE skipping passing any None values - return wrapper.get_dims(self.arr)[: self.ndim] + return wrapper.get_dims(self._arr)[: self.ndim] @property def offset(self) -> int: @@ -933,7 +928,7 @@ def offset(self) -> int: int The offset in number of elements. """ - return wrapper.get_offset(self.arr) + return wrapper.get_offset(self._arr) @property def strides(self) -> tuple[int, ...]: @@ -945,7 +940,7 @@ def strides(self) -> tuple[int, ...]: tuple[int, ...] The strides for each dimension. """ - return wrapper.get_strides(self.arr)[: self.ndim] + return wrapper.get_strides(self._arr)[: self.ndim] # TODO rename front_to_host or smth. Extend doc: move first element of array from gpu to cpu def scalar(self) -> int | float | bool | complex | None: # FIXME @@ -956,13 +951,13 @@ def scalar(self) -> int | float | bool | complex | None: # FIXME if self.is_empty(): return None - return wrapper.get_scalar(self.arr, self.dtype) + return wrapper.get_scalar(self._arr, self.dtype) def is_empty(self) -> bool: """ Check if the array is empty i.e. it has no elements. """ - return wrapper.is_empty(self.arr) + return wrapper.is_empty(self._arr) def to_list(self, row_major: bool = False) -> list[int | float | bool | complex]: if self.is_empty(): @@ -1003,11 +998,30 @@ def copy(self) -> Array: An identical copy of self. """ - return cast(Array, wrapper.copy_array(self.arr)) + return cast(Array, wrapper.copy_array(self._arr)) + + @property + def arr(self) -> AFArray: + return self._arr + + @classmethod + def from_afarray(cls, array: wrapper.AFArray) -> Array: + """ + Creates an instance of Array from an AFArray object. + + Parameters + ---------- + array: AFArray + The array object to wrap in the Array instance. - # @classmethod - # def from_afarray(cls, array: wrapper.AFArrayType) -> None: - # cls.arr = array + Returns + ------- + Array + An instance of Array wrapping the given array. + """ + out = cls() + out._arr = array + return out IndexKey = int | float | complex | bool | wrapper.ParallelRange | slice | tuple[int | slice, ...] | Array diff --git a/arrayfire/library/computer_vision.py b/arrayfire/library/computer_vision.py new file mode 100644 index 0000000..cb5a3f6 --- /dev/null +++ b/arrayfire/library/computer_vision.py @@ -0,0 +1,85 @@ +from typing import cast + +import arrayfire_wrapper.lib as wrapper + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array + + +def gloh( + image: Array, + /, + n_layers: int = 3, + contrast_threshold: float = 0.04, + edge_threshold: float = 10.0, + initial_sigma: float = 1.6, + dobule_input: bool = True, + intensity_scale: float = 1.0 / 255, + feature_ratio: float = 0.05, +) -> tuple[Array, Array]: + features, descriptors = wrapper.gloh( + image.arr, + n_layers, + contrast_threshold, + edge_threshold, + initial_sigma, + dobule_input, + intensity_scale, + feature_ratio, + ) + return Array.from_afarray(features), Array.from_afarray(descriptors) + + +def orb( + image: Array, + /, + fast_threshold: float = 20.0, + max_features: int = 400, + scale_factor: float = 1.5, + n_levels: int = 4, + blur_image: bool = False, +) -> tuple[Array, Array]: + features, descriptors = wrapper.orb(image.arr, fast_threshold, max_features, scale_factor, n_levels, blur_image) + return Array.from_afarray(features), Array.from_afarray(descriptors) + + +def sift( + image: Array, + /, + n_layers: int = 3, + contrast_threshold: float = 0.04, + edge_threshold: float = 10.0, + initial_sigma: float = 1.6, + dobule_input: bool = True, + intensity_scale: float = 1.0 / 255, + feature_ratio: float = 0.05, +) -> tuple[Array, Array]: + features, descriptors = wrapper.sift( + image.arr, + n_layers, + contrast_threshold, + edge_threshold, + initial_sigma, + dobule_input, + intensity_scale, + feature_ratio, + ) + return Array.from_afarray(features), Array.from_afarray(descriptors) + + +@afarray_as_array +def dog(image: Array, radius1: int, radius2: int, /) -> Array: + return cast(Array, wrapper.dog(image.arr, radius1, radius2)) + + +@afarray_as_array +def fast( + image: Array, + /, + fast_threshold: float = 20.0, + arc_length: int = 9, + non_max: bool = True, + feature_ratio: float = 0.05, + edge: int = 3, +) -> Array: + return cast(Array, wrapper.fast(image.arr, fast_threshold, arc_length, non_max, feature_ratio, edge)) diff --git a/arrayfire/library/constants.py b/arrayfire/library/constants.py index 2d6b717..e2a1dbb 100644 --- a/arrayfire/library/constants.py +++ b/arrayfire/library/constants.py @@ -2,6 +2,16 @@ import math -import arrayfire_wrapper as afw +import arrayfire_wrapper.lib as wrapper -pi = afw.lib.constant(math.pi, (1,), afw.float64) +import arrayfire as af + +pi = wrapper.constant(math.pi, (1,), af.float64) + +# Typing constants + +Scalar = int | float | complex | bool + +# Wrapper constants + +BinaryOperator = wrapper.BinaryOperator diff --git a/arrayfire/library/create_and_modify_array.py b/arrayfire/library/create_and_modify_array.py new file mode 100644 index 0000000..6de1168 --- /dev/null +++ b/arrayfire/library/create_and_modify_array.py @@ -0,0 +1,49 @@ +from typing import cast + +import arrayfire_wrapper.lib as wrapper + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array + + +@afarray_as_array +def moddims(array: Array, shape: tuple[int, ...]) -> Array: + """ + Modify the shape of the array without changing the data layout. + + Parameters + ---------- + array : af.Array + Multi-dimensional array to be reshaped. + + shape : tuple of int + The desired shape of the output array. It should be a tuple of integers + representing the dimensions of the output array. The product of these + dimensions must match the total number of elements in the input array. + + Returns + ------- + out : af.Array + - An array containing the same data as `array` with the specified shape. + - The total number of elements in `array` must match the product of the + dimensions specified in the `shape` tuple. + + Raises + ------ + ValueError + If the total number of elements in the input array does not match the + product of the dimensions specified in the `shape` tuple. + + Notes + ----- + This function modifies the shape of the input array without changing the + data layout. The resulting array will have the same data, but with a + different shape as specified by the `shape` parameter. + + Examples + -------- + >>> a = af.randu(2, 3, 4) # Create a random 3D array + >>> b = moddims(a, (6, 2)) # Reshape to a 2D array with 6 rows and 2 columns + """ + + return cast(Array, wrapper.moddims(array.arr, shape)) diff --git a/arrayfire/library/operators.py b/arrayfire/library/operators.py index 136cb96..0f2ddd2 100755 --- a/arrayfire/library/operators.py +++ b/arrayfire/library/operators.py @@ -67,7 +67,10 @@ def mod(x1: int | float | Array, x2: int | float | Array, /) -> Array: def pow(x1: int | float | Array, x2: int | float | Array, /) -> Array: _check_operands_fit_requirements(x1, x2) - return wrapper.pow(x1.arr, x2.arr) + x1_ = x1.arr if isinstance(x1, Array) else x1 + x2_ = x2.arr if isinstance(x2, Array) else x2 + + return cast(Array, wrapper.pow(x1_, x2_)) @afarray_as_array @@ -139,7 +142,7 @@ def clamp(x: Array, /, lo: float, hi: float) -> Array: def minof(x1: int | float | Array, x2: int | float | Array, /) -> Array: _check_operands_fit_requirements(x1, x2) - return wrapper.minof(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.minof(x1.arr, x2.arr)) @afarray_as_array diff --git a/arrayfire/library/signal_processing/__init__.py b/arrayfire/library/signal_processing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arrayfire/library/signal_processing/fft.py b/arrayfire/library/signal_processing/fft.py new file mode 100644 index 0000000..3babfa4 --- /dev/null +++ b/arrayfire/library/signal_processing/fft.py @@ -0,0 +1 @@ +# Note: check for fft_c2r updates if is_odd =True diff --git a/arrayfire/library/vector_algorithms/inclusive_scan_operations.py b/arrayfire/library/vector_algorithms/inclusive_scan_operations.py new file mode 100644 index 0000000..45be92f --- /dev/null +++ b/arrayfire/library/vector_algorithms/inclusive_scan_operations.py @@ -0,0 +1,94 @@ +from typing import cast + +from arrayfire_wrapper import lib as wrapper + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array +from arrayfire.library.constants import BinaryOperator + + +@afarray_as_array +def accum(array: Array, /, axis: int = 0) -> Array: + """ + Calculate the cumulative sum of elements along a specified dimension. + + Parameters + ---------- + array : af.Array + Multi-dimensional ArrayFire array. + + axis : int, optional, default: 0 + Dimension along which the cumulative sum is required. + + Returns + ------- + af.Array + An ArrayFire array of the same size as `array` containing the cumulative sum along the specified dimension. + + Note + ---- + If `axis` is not specified, the cumulative sum is calculated along the first dimension (default: 0). + """ + return cast(Array, wrapper.accum(array.arr, axis)) + + +@afarray_as_array +def scan( + array: Array, + /, + key: None | Array = None, + axis: int = 0, + op: BinaryOperator = BinaryOperator.ADD, + inclusive_scan: bool = True, +) -> Array: + """ + Perform a generalized scan of an array, optionally with a key. + + Parameters + ---------- + array : af.Array + Multi-dimensional ArrayFire array. + + key : af.Array, optional, default: None + Key array for generalized scan. If None, a standard scan is performed. + + axis : int, optional, default: 0 + Dimension along which the scan is performed. + + op : af.BINARYOP, optional, default: af.BINARYOP.ADD + Binary operation that the scan algorithm uses. Can be one of: + - af.BINARYOP.ADD + - af.BINARYOP.MUL + - af.BINARYOP.MIN + - af.BINARYOP.MAX + + inclusive_scan : bool, optional, default: True + Specifies if the scan is inclusive. + + Returns + ------- + out : af.Array + Array containing the result of the generalized scan. + """ + if key: + return cast(Array, wrapper.scan_by_key(key.arr, array.arr, axis, op, inclusive_scan)) + + return cast(Array, wrapper.scan(array.arr, axis, op, inclusive_scan)) + + +@afarray_as_array +def where(array: Array, /) -> Array: + """ + Find the indices of non-zero elements. + + Parameters + ---------- + array : af.Array + Multi-dimensional ArrayFire array. + + Returns + ------- + af.Array + Linear indices for non-zero elements. + """ + return cast(Array, wrapper.where(array.arr)) diff --git a/arrayfire/library/vector_algorithms/reduction_operations.py b/arrayfire/library/vector_algorithms/reduction_operations.py index 196604b..65be29f 100644 --- a/arrayfire/library/vector_algorithms/reduction_operations.py +++ b/arrayfire/library/vector_algorithms/reduction_operations.py @@ -70,13 +70,14 @@ def any_true(array: Array, axis: int | None = None) -> bool | Array: def sum(array: Array, /, *, axis: int | None = None, nan_value: float | None = None) -> int | float | complex | Array: + # FIXME documentation issues """ Calculate the sum of elements along a specified dimension or the entire array. Parameters ---------- array : Array - Multi-dimensional array to calculate the sum of. + The multi-dimensional array to calculate the sum of. axis : int or None, optional, default: None The dimension along which the sum is calculated. @@ -87,13 +88,13 @@ def sum(array: Array, /, *, axis: int | None = None, nan_value: float | None = N Returns ------- - result : Array or bool or scalar + Array or bool or scalar - If `axis` is None and `nan_value` is None, returns a boolean indicating if the sum contains NaN or Inf. - If `axis` is None and `nan_value` is not None, returns a boolean indicating if the sum contains NaN or - Inf after replacing NaN values. + Inf after replacing NaN values. - If `axis` is not None, returns an Array containing the sum along the specified dimension. - If `axis` is not None and `nan_value` is not None, returns an Array containing the sum along the specified - dimension after replacing NaN values. + dimension after replacing NaN values. """ if axis is None: @@ -106,3 +107,41 @@ def sum(array: Array, /, *, axis: int | None = None, nan_value: float | None = N return _reduce_to_array(wrapper.sum, array, axis) return _reduce_to_array(wrapper.sum_nan, array, axis, nan_value=nan_value) + + +def product( + array: Array, /, *, axis: int | None = None, nan_value: float | None = None +) -> int | float | complex | Array: + # FIXME documentation issues + """ + Calculate the product of all the elements along a specified dimension. + + Parameters + ---------- + array : Array + The multi-dimensional array to calculate the product of. + + axis : int or None, optional, default: None + The dimension along which the product is calculated. + If None, the product of all elements in the entire array is returned. + + nan_value : float or None, optional, default: None + The value to replace NaN (Not-a-Number) values in the array before computing the product. + If None, NaN values are ignored. + + Returns + ------- + Array or scalar number + The product of all elements in `array` along dimension `axis`. + If `axis` is `None`, the product of the entire array is returned. + """ + if axis is None: + if nan_value is None: + return wrapper.product_all(array.arr) + + return wrapper.product_nan_all(array.arr, nan_value) + + if nan_value is None: + return _reduce_to_array(wrapper.product, array, axis) + + return _reduce_to_array(wrapper.product_nan, array, axis, nan_value=nan_value) diff --git a/tests/vector_algorithms/test_inclusive_scan_operations.py b/tests/vector_algorithms/test_inclusive_scan_operations.py new file mode 100644 index 0000000..e7ef32a --- /dev/null +++ b/tests/vector_algorithms/test_inclusive_scan_operations.py @@ -0,0 +1,32 @@ +# from typing import Union +# import arrayfire as af +# import pytest + +# # Fixture to create a sample ArrayFire array for testing +# @pytest.fixture +# def sample_array() -> af.Array: +# return af.randu(4, 3) + +# # Test cases +# def test_accum_default_axis(sample_array: af.Array) -> None: +# result = af.accum(sample_array) +# expected = af.moddims(af.scan(sample_array, af.BINARY_ADD, 0), sample_array.dims()) +# af.assert_allclose(result, expected) + +# def test_accum_custom_axis(sample_array: af.Array) -> None: +# result = af.accum(sample_array, axis=1) +# expected = af.moddims(af.scan(sample_array, af.BINARY_ADD, 1), sample_array.dims()) +# af.assert_allclose(result, expected) + +# def test_accum_custom_axis_2D_array() -> None: +# # Test with a 2D array, specifying axis=1 +# array = af.Array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) +# result = af.accum(array, axis=1) +# expected = af.Array([[1, 3, 6], [4, 9, 15], [7, 15, 24]]) +# af.assert_allclose(result, expected) + +# def test_accum_custom_axis_invalid_axis() -> None: +# # Test with an invalid axis +# array = af.randu(3, 4) +# with pytest.raises(ValueError, match="Invalid axis"): +# accum(array, axis=2) From b1b4d1e6919fb1a64a4b3d72ae45a1a1cc76366b Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 30 Jan 2024 05:36:21 +0200 Subject: [PATCH 06/16] Add vector algorithms --- arrayfire/__init__.py | 44 ++- arrayfire/library/computer_vision.py | 39 ++ arrayfire/library/constants.py | 1 + arrayfire/library/operators.py | 2 - arrayfire/library/vector_algorithms.py | 362 ++++++++++++++++++ .../library/vector_algorithms/__init__.py | 3 - .../inclusive_scan_operations.py | 94 ----- .../vector_algorithms/reduction_operations.py | 147 ------- 8 files changed, 444 insertions(+), 248 deletions(-) create mode 100644 arrayfire/library/vector_algorithms.py delete mode 100644 arrayfire/library/vector_algorithms/__init__.py delete mode 100644 arrayfire/library/vector_algorithms/inclusive_scan_operations.py delete mode 100644 arrayfire/library/vector_algorithms/reduction_operations.py diff --git a/arrayfire/__init__.py b/arrayfire/__init__.py index 027d0d3..eb71fc6 100755 --- a/arrayfire/__init__.py +++ b/arrayfire/__init__.py @@ -253,6 +253,46 @@ from arrayfire.library.random import randu -__all__ += ["all_true", "any_true"] +__all__ += [ + "accum", + "scan", + "where", + "all_true", + "any_true", + "sum", + "product", + "count", + "imax", + "max", + "imin", + "min", + "diff1", + "diff2", + "gradient", + "set_intersect", + "set_union", + "set_unique", + "sort", +] -from arrayfire.library.vector_algorithms import all_true, any_true +from arrayfire.library.vector_algorithms import ( + accum, + all_true, + any_true, + count, + diff1, + diff2, + gradient, + imax, + imin, + max, + min, + product, + scan, + set_intersect, + set_union, + set_unique, + sort, + sum, + where, +) diff --git a/arrayfire/library/computer_vision.py b/arrayfire/library/computer_vision.py index cb5a3f6..0650166 100644 --- a/arrayfire/library/computer_vision.py +++ b/arrayfire/library/computer_vision.py @@ -4,6 +4,7 @@ from arrayfire import Array from arrayfire.array_object import afarray_as_array +from arrayfire.library.constants import Match def gloh( @@ -83,3 +84,41 @@ def fast( edge: int = 3, ) -> Array: return cast(Array, wrapper.fast(image.arr, fast_threshold, arc_length, non_max, feature_ratio, edge)) + + +@afarray_as_array +def harris( + image: Array, + /, + max_corners: int = 500, + min_response: float = 1e5, + sigma: float = 1.0, + block_size: int = 0, + k_threshold: float = 0, +) -> Array: + return cast(Array, wrapper.harris(image.arr, max_corners, min_response, sigma, block_size, k_threshold)) + + +@afarray_as_array +def susan( + image: Array, + /, + radius: int = 500, + diff_threshold: float = 1e5, + geom_threshold: float = 1.0, + feature_ratio: float = 0.05, + edge: int = 3, +) -> Array: + return cast(Array, wrapper.susan(image.arr, radius, diff_threshold, geom_threshold, feature_ratio, edge)) + + +def hamming_matcher(query: Array, train: Array, /, axis: int = 0, n_nearest: int = 1) -> tuple[Array, Array]: + indices, distance = wrapper.hamming_matcher(query.arr, train.arr, axis, n_nearest) + return Array.from_afarray(indices), Array.from_afarray(distance) + + +def nearest_neighbour( + query: Array, train: Array, /, axis: int = 0, n_nearest: int = 1, match_type: Match = Match.SSD +) -> tuple[Array, Array]: + indices, distance = wrapper.nearest_neighbour(query.arr, train.arr, axis, n_nearest, match_type) + return Array.from_afarray(indices), Array.from_afarray(distance) diff --git a/arrayfire/library/constants.py b/arrayfire/library/constants.py index e2a1dbb..dec0353 100644 --- a/arrayfire/library/constants.py +++ b/arrayfire/library/constants.py @@ -15,3 +15,4 @@ # Wrapper constants BinaryOperator = wrapper.BinaryOperator +Match = wrapper.Match diff --git a/arrayfire/library/operators.py b/arrayfire/library/operators.py index 0f2ddd2..b9ec06e 100755 --- a/arrayfire/library/operators.py +++ b/arrayfire/library/operators.py @@ -6,8 +6,6 @@ from arrayfire import Array from arrayfire.array_object import afarray_as_array - -# from arrayfire._backup_backend import _clib_wrapper as wrapper from arrayfire.dtypes import is_complex_dtype diff --git a/arrayfire/library/vector_algorithms.py b/arrayfire/library/vector_algorithms.py new file mode 100644 index 0000000..e910650 --- /dev/null +++ b/arrayfire/library/vector_algorithms.py @@ -0,0 +1,362 @@ +__all__ = [ + "accum", + "scan", + "where", + "all_true", + "any_true", + "sum", + "product", + "count", + "imax", + "max", + "imin", + "min", + "diff1", + "diff2", + "gradient", + "set_intersect", + "set_union", + "set_unique", + "sort", +] + +from typing import cast + +from arrayfire_wrapper import lib as wrapper + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array +from arrayfire.library.constants import BinaryOperator + + +@afarray_as_array +def accum(array: Array, /, axis: int = 0) -> Array: + """ + Calculate the cumulative sum of elements along a specified dimension. + + Parameters + ---------- + array : af.Array + Multi-dimensional ArrayFire array. + + axis : int, optional, default: 0 + Dimension along which the cumulative sum is required. + + Returns + ------- + af.Array + An ArrayFire array of the same size as `array` containing the cumulative sum along the specified dimension. + + Note + ---- + If `axis` is not specified, the cumulative sum is calculated along the first dimension (default: 0). + """ + return cast(Array, wrapper.accum(array.arr, axis)) + + +@afarray_as_array +def scan( + array: Array, + /, + keys: None | Array = None, + axis: int = 0, + op: BinaryOperator = BinaryOperator.ADD, + inclusive_scan: bool = True, +) -> Array: + """ + Perform a generalized scan of an array, optionally with a key. + + Parameters + ---------- + array : af.Array + Multi-dimensional ArrayFire array. + + keys : af.Array, optional, default: None + Keys array for generalized scan. If None, a standard scan is performed. + + axis : int, optional, default: 0 + Dimension along which the scan is performed. + + op : af.BINARYOP, optional, default: af.BINARYOP.ADD + Binary operation that the scan algorithm uses. Can be one of: + - af.BINARYOP.ADD + - af.BINARYOP.MUL + - af.BINARYOP.MIN + - af.BINARYOP.MAX + + inclusive_scan : bool, optional, default: True + Specifies if the scan is inclusive. + + Returns + ------- + out : af.Array + Array containing the result of the generalized scan. + """ + if keys: + return cast(Array, wrapper.scan_by_key(keys.arr, array.arr, axis, op, inclusive_scan)) + + return cast(Array, wrapper.scan(array.arr, axis, op, inclusive_scan)) + + +@afarray_as_array +def where(array: Array, /) -> Array: + """ + Find the indices of non-zero elements. + + Parameters + ---------- + array : af.Array + Multi-dimensional ArrayFire array. + + Returns + ------- + af.Array + Linear indices for non-zero elements. + """ + return cast(Array, wrapper.where(array.arr)) + + +def all_true(array: Array, axis: int | None = None) -> bool | Array: + """ + Check if all the elements along a specified dimension are true. + + Parameters + ---------- + array : Array + Multi-dimensional ArrayFire array. + + axis : int, optional, default: None + Dimension along which the product is required. + + Returns + ------- + bool | Array + An ArrayFire array containing True if all elements in `array` along the specified dimension are True. + If `axis` is `None`, the output is True if `array` does not have any zeros, else False. + + Note + ---- + If `axis` is `None`, output is True if the array does not have any zeros, else False. + """ + if axis is None: + return bool(wrapper.all_true_all(array.arr)) + + return Array.from_afarray(wrapper.all_true(array.arr, axis)) + + +def any_true(array: Array, axis: int | None = None) -> bool | Array: + """ + Check if any of the elements along a specified dimension are true. + + Parameters + ---------- + array : Array + Multi-dimensional ArrayFire array. + + axis : int, optional, default: None + Dimension along which the product is required. + + Returns + ------- + bool | Array + An ArrayFire array containing True if any of the elements in `array` along the specified dimension are True. + If `axis` is `None`, the output is True if `array` does not have any zeros, else False. + + Note + ---- + If `axis` is `None`, output is True if the array does not have any zeros, else False. + """ + if axis is None: + return bool(wrapper.any_true_all(array.arr)) + + return Array.from_afarray(wrapper.any_true(array.arr, axis)) + + +def sum(array: Array, /, *, axis: int | None = None, nan_value: float | None = None) -> int | float | complex | Array: + # FIXME documentation issues + """ + Calculate the sum of elements along a specified dimension or the entire array. + + Parameters + ---------- + array : Array + The multi-dimensional array to calculate the sum of. + + axis : int or None, optional, default: None + The dimension along which the sum is calculated. + If None, the sum of all elements in the entire array is calculated. + + nan_value : float or None, optional, default: None + The value to replace NaN (Not-a-Number) values in the array before summing. If None, NaN values are ignored. + + Returns + ------- + Array or bool or scalar + - If `axis` is None and `nan_value` is None, returns a boolean indicating if the sum contains NaN or Inf. + - If `axis` is None and `nan_value` is not None, returns a boolean indicating if the sum contains NaN or + Inf after replacing NaN values. + - If `axis` is not None, returns an Array containing the sum along the specified dimension. + - If `axis` is not None and `nan_value` is not None, returns an Array containing the sum along the specified + dimension after replacing NaN values. + """ + + if axis is None: + if nan_value is None: + return wrapper.sum_all(array.arr) + + return wrapper.sum_nan_all(array.arr, nan_value) + + if nan_value is not None: + return Array.from_afarray(wrapper.sum(array.arr, axis)) + + return Array.from_afarray(wrapper.sum_nan(array.arr, axis, nan_value=nan_value)) # type: ignore[call-arg] + + +def product( + array: Array, /, *, axis: int | None = None, nan_value: float | None = None +) -> int | float | complex | Array: + # FIXME documentation issues + """ + Calculate the product of all the elements along a specified dimension. + + Parameters + ---------- + array : Array + The multi-dimensional array to calculate the product of. + + axis : int or None, optional, default: None + The dimension along which the product is calculated. + If None, the product of all elements in the entire array is returned. + + nan_value : float or None, optional, default: None + The value to replace NaN (Not-a-Number) values in the array before computing the product. + If None, NaN values are ignored. + + Returns + ------- + Array or scalar number + The product of all elements in `array` along dimension `axis`. + If `axis` is `None`, the product of the entire array is returned. + """ + if axis is None: + if nan_value is None: + return wrapper.product_all(array.arr) + + return wrapper.product_nan_all(array.arr, nan_value) + + if nan_value is None: + return Array.from_afarray(wrapper.product(array.arr, axis)) + + return Array.from_afarray(wrapper.product_nan(array.arr, axis, nan_value=nan_value)) # type: ignore[call-arg] + + +def count( + array: Array, /, *, axis: int | None = None, keys: Array | None = None +) -> int | float | complex | Array | tuple[Array, Array]: + if keys: + axis_ = -1 if axis is None else axis + key, value = wrapper.count_by_key(keys.arr, array.arr, axis_) + return Array.from_afarray(key), Array.from_afarray(value) + + if axis is None: + return wrapper.count_all(array.arr) + + return Array.from_afarray(wrapper.count(array.arr, axis)) + + +def imax(array: Array, /, *, axis: int | None = None) -> tuple[int | float | complex, int] | tuple[Array, Array]: + if axis is None: + return wrapper.imax_all(array.arr) + + maximum, location = wrapper.imax(array.arr, axis) + return Array.from_afarray(maximum), Array.from_afarray(location) + + +def max( + array: Array, /, *, axis: int | None = None, keys: Array | None, ragged_len: Array | None +) -> int | float | complex | Array | tuple[Array, Array]: + if keys and ragged_len: + raise RuntimeError("To process ragged max function, the keys value should be None and vice versa.") + + if keys: + axis_ = -1 if axis is None else axis + key, value = wrapper.max_by_key(keys.arr, array.arr, axis_) + return Array.from_afarray(key), Array.from_afarray(value) + + if ragged_len: + axis_ = -1 if axis is None else axis + values, indices = wrapper.max_ragged(array.arr, ragged_len.arr, axis_) + return Array.from_afarray(values), Array.from_afarray(indices) + + if axis is None: + return wrapper.max_all(array.arr) + + return Array.from_afarray(wrapper.max(array.arr, axis)) + + +def imin(array: Array, /, *, axis: int | None = None) -> tuple[int | float | complex, int] | tuple[Array, Array]: + if axis is None: + return wrapper.imin_all(array.arr) + + minimum, location = wrapper.imin(array.arr, axis) + return Array.from_afarray(minimum), Array.from_afarray(location) + + +def min(array: Array, /, *, axis: int | None = None) -> int | float | complex | Array: + if axis is None: + return wrapper.min_all(array.arr) + + return Array.from_afarray(wrapper.min(array.arr, axis)) + + +@afarray_as_array +def diff1(array: Array, /, axis: int = 0) -> Array: + return cast(Array, wrapper.diff1(array.arr, axis)) + + +@afarray_as_array +def diff2(array: Array, /, axis: int = 0) -> Array: + return cast(Array, wrapper.diff2(array.arr, axis)) + + +def gradient(array: Array, /) -> tuple[Array, Array]: + dx, dy = wrapper.gradient(array.arr) + return Array.from_afarray(dx), Array.from_afarray(dy) + + +@afarray_as_array +def set_intersect(x: Array, y: Array, /, *, is_unique: bool = False) -> Array: + return cast(Array, wrapper.set_intersect(x.arr, y.arr, is_unique)) + + +@afarray_as_array +def set_union(x: Array, y: Array, /, *, is_unique: bool = False) -> Array: + return cast(Array, wrapper.set_union(x.arr, y.arr, is_unique)) + + +@afarray_as_array +def set_unique(array: Array, /, *, is_sorted: bool = False) -> Array: + return cast(Array, wrapper.set_unique(array.arr, is_sorted)) + + +def sort( + array: Array, + /, + axis: int = 0, + is_ascending: bool = True, + *, + keys: Array | None = None, + is_index_array: bool = False, +) -> Array | tuple[Array, Array]: + if keys and is_index_array: + raise RuntimeError("Could not process sorting by keys when `is_index_array` is True. Select only one option.") + + if keys: + key, value = wrapper.sort_by_key(keys.arr, array.arr, axis, is_ascending) + return Array.from_afarray(key), Array.from_afarray(value) + + if is_index_array: + values, indices = wrapper.sort_index(array.arr, axis, is_ascending) + return Array.from_afarray(values), Array.from_afarray(indices) + + return Array.from_afarray(wrapper.sort(array.arr, axis, is_ascending)) diff --git a/arrayfire/library/vector_algorithms/__init__.py b/arrayfire/library/vector_algorithms/__init__.py deleted file mode 100644 index 06a3bd4..0000000 --- a/arrayfire/library/vector_algorithms/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -__all__ = ["any_true", "all_true", "sum"] - -from .reduction_operations import all_true, any_true, sum diff --git a/arrayfire/library/vector_algorithms/inclusive_scan_operations.py b/arrayfire/library/vector_algorithms/inclusive_scan_operations.py deleted file mode 100644 index 45be92f..0000000 --- a/arrayfire/library/vector_algorithms/inclusive_scan_operations.py +++ /dev/null @@ -1,94 +0,0 @@ -from typing import cast - -from arrayfire_wrapper import lib as wrapper - -from arrayfire import Array -from arrayfire.array_object import afarray_as_array -from arrayfire.library.constants import BinaryOperator - - -@afarray_as_array -def accum(array: Array, /, axis: int = 0) -> Array: - """ - Calculate the cumulative sum of elements along a specified dimension. - - Parameters - ---------- - array : af.Array - Multi-dimensional ArrayFire array. - - axis : int, optional, default: 0 - Dimension along which the cumulative sum is required. - - Returns - ------- - af.Array - An ArrayFire array of the same size as `array` containing the cumulative sum along the specified dimension. - - Note - ---- - If `axis` is not specified, the cumulative sum is calculated along the first dimension (default: 0). - """ - return cast(Array, wrapper.accum(array.arr, axis)) - - -@afarray_as_array -def scan( - array: Array, - /, - key: None | Array = None, - axis: int = 0, - op: BinaryOperator = BinaryOperator.ADD, - inclusive_scan: bool = True, -) -> Array: - """ - Perform a generalized scan of an array, optionally with a key. - - Parameters - ---------- - array : af.Array - Multi-dimensional ArrayFire array. - - key : af.Array, optional, default: None - Key array for generalized scan. If None, a standard scan is performed. - - axis : int, optional, default: 0 - Dimension along which the scan is performed. - - op : af.BINARYOP, optional, default: af.BINARYOP.ADD - Binary operation that the scan algorithm uses. Can be one of: - - af.BINARYOP.ADD - - af.BINARYOP.MUL - - af.BINARYOP.MIN - - af.BINARYOP.MAX - - inclusive_scan : bool, optional, default: True - Specifies if the scan is inclusive. - - Returns - ------- - out : af.Array - Array containing the result of the generalized scan. - """ - if key: - return cast(Array, wrapper.scan_by_key(key.arr, array.arr, axis, op, inclusive_scan)) - - return cast(Array, wrapper.scan(array.arr, axis, op, inclusive_scan)) - - -@afarray_as_array -def where(array: Array, /) -> Array: - """ - Find the indices of non-zero elements. - - Parameters - ---------- - array : af.Array - Multi-dimensional ArrayFire array. - - Returns - ------- - af.Array - Linear indices for non-zero elements. - """ - return cast(Array, wrapper.where(array.arr)) diff --git a/arrayfire/library/vector_algorithms/reduction_operations.py b/arrayfire/library/vector_algorithms/reduction_operations.py deleted file mode 100644 index 65be29f..0000000 --- a/arrayfire/library/vector_algorithms/reduction_operations.py +++ /dev/null @@ -1,147 +0,0 @@ -from collections.abc import Callable -from typing import Any, cast - -from arrayfire_wrapper import lib as wrapper - -from arrayfire import Array -from arrayfire.array_object import afarray_as_array - - -@afarray_as_array -def _reduce_to_array(func: Callable, array: Array, axis: int, /, **kwargs: Any) -> Array: - result = func(array.arr, axis, **kwargs) - return cast(Array, result) - - -def all_true(array: Array, axis: int | None = None) -> bool | Array: - """ - Check if all the elements along a specified dimension are true. - - Parameters - ---------- - array : Array - Multi-dimensional ArrayFire array. - - axis : int, optional, default: None - Dimension along which the product is required. - - Returns - ------- - bool | Array - An ArrayFire array containing True if all elements in `array` along the specified dimension are True. - If `axis` is `None`, the output is True if `array` does not have any zeros, else False. - - Note - ---- - If `axis` is `None`, output is True if the array does not have any zeros, else False. - """ - if axis is None: - return bool(wrapper.all_true_all(array.arr)) - - return _reduce_to_array(wrapper.all_true, array, axis) - - -def any_true(array: Array, axis: int | None = None) -> bool | Array: - """ - Check if any of the elements along a specified dimension are true. - - Parameters - ---------- - array : Array - Multi-dimensional ArrayFire array. - - axis : int, optional, default: None - Dimension along which the product is required. - - Returns - ------- - bool | Array - An ArrayFire array containing True if any of the elements in `array` along the specified dimension are True. - If `axis` is `None`, the output is True if `array` does not have any zeros, else False. - - Note - ---- - If `axis` is `None`, output is True if the array does not have any zeros, else False. - """ - if axis is None: - return bool(wrapper.any_true_all(array.arr)) - - return _reduce_to_array(wrapper.any_true, array, axis) - - -def sum(array: Array, /, *, axis: int | None = None, nan_value: float | None = None) -> int | float | complex | Array: - # FIXME documentation issues - """ - Calculate the sum of elements along a specified dimension or the entire array. - - Parameters - ---------- - array : Array - The multi-dimensional array to calculate the sum of. - - axis : int or None, optional, default: None - The dimension along which the sum is calculated. - If None, the sum of all elements in the entire array is calculated. - - nan_value : float or None, optional, default: None - The value to replace NaN (Not-a-Number) values in the array before summing. If None, NaN values are ignored. - - Returns - ------- - Array or bool or scalar - - If `axis` is None and `nan_value` is None, returns a boolean indicating if the sum contains NaN or Inf. - - If `axis` is None and `nan_value` is not None, returns a boolean indicating if the sum contains NaN or - Inf after replacing NaN values. - - If `axis` is not None, returns an Array containing the sum along the specified dimension. - - If `axis` is not None and `nan_value` is not None, returns an Array containing the sum along the specified - dimension after replacing NaN values. - """ - - if axis is None: - if nan_value is None: - return wrapper.sum_all(array.arr) - - return wrapper.sum_nan_all(array.arr, nan_value) - - if nan_value is None: - return _reduce_to_array(wrapper.sum, array, axis) - - return _reduce_to_array(wrapper.sum_nan, array, axis, nan_value=nan_value) - - -def product( - array: Array, /, *, axis: int | None = None, nan_value: float | None = None -) -> int | float | complex | Array: - # FIXME documentation issues - """ - Calculate the product of all the elements along a specified dimension. - - Parameters - ---------- - array : Array - The multi-dimensional array to calculate the product of. - - axis : int or None, optional, default: None - The dimension along which the product is calculated. - If None, the product of all elements in the entire array is returned. - - nan_value : float or None, optional, default: None - The value to replace NaN (Not-a-Number) values in the array before computing the product. - If None, NaN values are ignored. - - Returns - ------- - Array or scalar number - The product of all elements in `array` along dimension `axis`. - If `axis` is `None`, the product of the entire array is returned. - """ - if axis is None: - if nan_value is None: - return wrapper.product_all(array.arr) - - return wrapper.product_nan_all(array.arr, nan_value) - - if nan_value is None: - return _reduce_to_array(wrapper.product, array, axis) - - return _reduce_to_array(wrapper.product_nan, array, axis, nan_value=nan_value) From b10302c2a393269b6f0c5f9ac59fbde04bb7bde5 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 31 Jan 2024 06:18:47 +0200 Subject: [PATCH 07/16] Add tones of code --- README.md | 18 + arrayfire/__init__.py | 2 +- arrayfire/array_api/_elementwise_functions.py | 104 +- arrayfire/array_object.py | 14 +- arrayfire/library/constants.py | 9 +- arrayfire/library/input_and_output.py | 60 ++ arrayfire/library/interface_functions.py | 6 + arrayfire/library/linear_algebra.py | 125 +++ arrayfire/library/machine_learning.py | 37 + ...operators.py => mathematical_functions.py} | 902 +++++++++--------- arrayfire/library/statistics.py | 72 ++ arrayfire/library/unified_api_functions.py | 23 + tests/test_operators.py | 10 +- 13 files changed, 861 insertions(+), 521 deletions(-) create mode 100644 arrayfire/library/input_and_output.py create mode 100644 arrayfire/library/interface_functions.py create mode 100644 arrayfire/library/linear_algebra.py create mode 100644 arrayfire/library/machine_learning.py rename arrayfire/library/{operators.py => mathematical_functions.py} (96%) mode change 100755 => 100644 create mode 100644 arrayfire/library/statistics.py create mode 100644 arrayfire/library/unified_api_functions.py diff --git a/README.md b/README.md index c72eb14..2615881 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,20 @@ # arrayfire-py Arrayfire python wrapper + +## Coverage + +- [x] Computer Vision +- [] Events +- [] Functions to Create and Modify Arrays +- [] Functions to Work with Internal Array Layout +- [] Image Processing + - [] Features +- [x] Input and Output Functions +- [x] Interface Functions +- [x] Linear Algebra +- [x] Machine Learning +- [x] Mathematical Functions +- [] Signal Processing +- [x] Statistics +- [x] Unified API Functions +- [x] Vector Algorithms diff --git a/arrayfire/__init__.py b/arrayfire/__init__.py index eb71fc6..d396714 100755 --- a/arrayfire/__init__.py +++ b/arrayfire/__init__.py @@ -173,7 +173,7 @@ ] -from .library.operators import ( +from .library.mathematical_functions import ( abs, acos, acosh, diff --git a/arrayfire/array_api/_elementwise_functions.py b/arrayfire/array_api/_elementwise_functions.py index 36f2bef..d981d1f 100755 --- a/arrayfire/array_api/_elementwise_functions.py +++ b/arrayfire/array_api/_elementwise_functions.py @@ -1,104 +1,104 @@ from __future__ import annotations -from arrayfire.library import operators +from arrayfire.library import mathematical_functions from ._array_object import Array def abs(x: Array, /) -> Array: - return Array._new(operators.abs(x._array.arr)) + return Array._new(mathematical_functions.abs(x._array.arr)) def acos(x: Array, /) -> Array: - return Array._new(operators.acos(x._array.arr)) + return Array._new(mathematical_functions.acos(x._array.arr)) def acosh(x: Array, /) -> Array: - return Array._new(operators.acosh(x._array.arr)) + return Array._new(mathematical_functions.acosh(x._array.arr)) def add(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.add(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.add(x1._array.arr, x2._array.arr)) def asin(x: Array, /) -> Array: - return Array._new(operators.asin(x._array.arr)) + return Array._new(mathematical_functions.asin(x._array.arr)) def asinh(x: Array, /) -> Array: - return Array._new(operators.asinh(x._array.arr)) + return Array._new(mathematical_functions.asinh(x._array.arr)) def atan(x: Array, /) -> Array: - return Array._new(operators.atan(x._array.arr)) + return Array._new(mathematical_functions.atan(x._array.arr)) def atan2(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.atan2(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.atan2(x1._array.arr, x2._array.arr)) def atanh(x: Array, /) -> Array: - return Array._new(operators.atanh(x._array.arr)) + return Array._new(mathematical_functions.atanh(x._array.arr)) def bitwise_and(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.bitand(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.bitand(x1._array.arr, x2._array.arr)) def bitwise_left_shift(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.bitshiftl(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.bitshiftl(x1._array.arr, x2._array.arr)) def bitwise_invert(x: Array, /) -> Array: - return Array._new(operators.bitnot(x._array.arr)) + return Array._new(mathematical_functions.bitnot(x._array.arr)) def bitwise_or(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.bitor(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.bitor(x1._array.arr, x2._array.arr)) def bitwise_right_shift(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.bitshiftr(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.bitshiftr(x1._array.arr, x2._array.arr)) def bitwise_xor(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.bitxor(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.bitxor(x1._array.arr, x2._array.arr)) def ceil(x: Array, /) -> Array: - return Array._new(operators.ceil(x._array.arr)) + return Array._new(mathematical_functions.ceil(x._array.arr)) def conj(x: Array, /) -> Array: - return Array._new(operators.conjg(x._array.arr)) + return Array._new(mathematical_functions.conjg(x._array.arr)) def cos(x: Array, /) -> Array: - return Array._new(operators.cos(x._array.arr)) + return Array._new(mathematical_functions.cos(x._array.arr)) def cosh(x: Array, /) -> Array: - return Array._new(operators.cosh(x._array.arr)) + return Array._new(mathematical_functions.cosh(x._array.arr)) def divide(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.div(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.div(x1._array.arr, x2._array.arr)) def equal(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.eq(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.eq(x1._array.arr, x2._array.arr)) def exp(x: Array, /) -> Array: - return Array._new(operators.exp(x._array.arr)) + return Array._new(mathematical_functions.exp(x._array.arr)) def expm1(x: Array, /) -> Array: - return Array._new(operators.expm1(x._array.arr)) + return Array._new(mathematical_functions.expm1(x._array.arr)) def floor(x: Array, /) -> Array: - return Array._new(operators.floor(x._array.arr)) + return Array._new(mathematical_functions.floor(x._array.arr)) def floor_divide(x1: Array, x2: Array, /) -> Array: @@ -106,15 +106,15 @@ def floor_divide(x1: Array, x2: Array, /) -> Array: def greater(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.gt(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.gt(x1._array.arr, x2._array.arr)) def greater_equal(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.ge(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.ge(x1._array.arr, x2._array.arr)) def imag(x: Array, /) -> Array: - return Array._new(operators.imag(x._array.arr)) + return Array._new(mathematical_functions.imag(x._array.arr)) def isfinite(x: Array, /) -> Array: @@ -122,35 +122,35 @@ def isfinite(x: Array, /) -> Array: def isinf(x: Array, /) -> Array: - return Array._new(operators.isinf(x._array.arr)) + return Array._new(mathematical_functions.isinf(x._array.arr)) def isnan(x: Array, /) -> Array: - return Array._new(operators.isnan(x._array.arr)) + return Array._new(mathematical_functions.isnan(x._array.arr)) def less(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.lt(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.lt(x1._array.arr, x2._array.arr)) def less_equal(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.le(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.le(x1._array.arr, x2._array.arr)) def log(x: Array, /) -> Array: - return Array._new(operators.log(x._array.arr)) + return Array._new(mathematical_functions.log(x._array.arr)) def log1p(x: Array, /) -> Array: - return Array._new(operators.log1p(x._array.arr)) + return Array._new(mathematical_functions.log1p(x._array.arr)) def log2(x: Array, /) -> Array: - return Array._new(operators.log2(x._array.arr)) + return Array._new(mathematical_functions.log2(x._array.arr)) def log10(x: Array, /) -> Array: - return Array._new(operators.log10(x._array.arr)) + return Array._new(mathematical_functions.log10(x._array.arr)) def logaddexp(x1: Array, x2: Array) -> Array: @@ -158,15 +158,15 @@ def logaddexp(x1: Array, x2: Array) -> Array: def logical_and(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.land(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.land(x1._array.arr, x2._array.arr)) def logical_not(x: Array, /) -> Array: - return Array._new(operators.lnot(x._array.arr)) + return Array._new(mathematical_functions.lnot(x._array.arr)) def logical_or(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.lor(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.lor(x1._array.arr, x2._array.arr)) def logical_xor(x1: Array, x2: Array, /) -> Array: @@ -174,7 +174,7 @@ def logical_xor(x1: Array, x2: Array, /) -> Array: def multiply(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.mul(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.mul(x1._array.arr, x2._array.arr)) def negative(x: Array, /) -> Array: @@ -190,31 +190,31 @@ def positive(x: Array, /) -> Array: def pow(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.pow(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.pow(x1._array.arr, x2._array.arr)) def real(x: Array, /) -> Array: - return Array._new(operators.real(x._array.arr)) + return Array._new(mathematical_functions.real(x._array.arr)) def remainder(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.rem(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.rem(x1._array.arr, x2._array.arr)) def round(x: Array, /) -> Array: - return Array._new(operators.round(x._array.arr)) + return Array._new(mathematical_functions.round(x._array.arr)) def sign(x: Array, /) -> Array: - return Array._new(operators.sign(x._array.arr)) + return Array._new(mathematical_functions.sign(x._array.arr)) def sin(x: Array, /) -> Array: - return Array._new(operators.sin(x._array.arr)) + return Array._new(mathematical_functions.sin(x._array.arr)) def sinh(x: Array, /) -> Array: - return Array._new(operators.sinh(x._array.arr)) + return Array._new(mathematical_functions.sinh(x._array.arr)) def square(x: Array, /) -> Array: @@ -222,20 +222,20 @@ def square(x: Array, /) -> Array: def sqrt(x: Array, /) -> Array: - return Array._new(operators.sqrt(x._array.arr)) + return Array._new(mathematical_functions.sqrt(x._array.arr)) def subtract(x1: Array, x2: Array, /) -> Array: - return Array._new(operators.sub(x1._array.arr, x2._array.arr)) + return Array._new(mathematical_functions.sub(x1._array.arr, x2._array.arr)) def tan(x: Array, /) -> Array: - return Array._new(operators.tan(x._array.arr)) + return Array._new(mathematical_functions.tan(x._array.arr)) def tanh(x: Array, /) -> Array: - return Array._new(operators.tanh(x._array.arr)) + return Array._new(mathematical_functions.tanh(x._array.arr)) def trunc(x: Array, /) -> Array: - return Array._new(operators.trunc(x._array.arr)) + return Array._new(mathematical_functions.trunc(x._array.arr)) diff --git a/arrayfire/array_object.py b/arrayfire/array_object.py index c9705e2..d550ae4 100755 --- a/arrayfire/array_object.py +++ b/arrayfire/array_object.py @@ -1068,13 +1068,13 @@ def _get_processed_index(key: IndexKey, shape: tuple[int, ...]) -> tuple[int, .. return (_index_to_afindex(key, shape[0]),) + shape[1:] -def _index_to_afindex(key: int | float | complex | bool | slice | wrapper.ParallelRange | Array, dim: int) -> int: +def _index_to_afindex(key: int | float | complex | bool | slice | wrapper.ParallelRange | Array, axis: int) -> int: if isinstance(key, int | float | complex | bool): out = 1 elif isinstance(key, slice): - out = _slice_to_length(key, dim) + out = _slice_to_length(key, axis) elif isinstance(key, wrapper.ParallelRange): - out = _slice_to_length(key.S, dim) + out = _slice_to_length(key.S, axis) elif isinstance(key, Array): if key.dtype == afbool: from arrayfire.library.vector_algorithms import sum as af_sum @@ -1088,16 +1088,16 @@ def _index_to_afindex(key: int | float | complex | bool | slice | wrapper.Parall return out -def _slice_to_length(key: slice, dim: int) -> int: +def _slice_to_length(key: slice, axis: int) -> int: if key.start is None: start = 0 elif key.start < 0: - start = dim - key.start + start = axis - key.start if key.stop is None: - stop = dim + stop = axis elif key.stop < 0: - stop = dim - key.stop + stop = axis - key.stop if key.step is None: step = 1 diff --git a/arrayfire/library/constants.py b/arrayfire/library/constants.py index dec0353..6bfe47d 100644 --- a/arrayfire/library/constants.py +++ b/arrayfire/library/constants.py @@ -1,5 +1,9 @@ __all__ = ["pi"] +from arrayfire_wrapper.lib import BinaryOperator, ConvGradient, ImageFormat, Match, MatProp, Norm, TopK, VarianceBias + +__all__ += ["Match", "MatProp", "BinaryOperator", "Norm", "ConvGradient", "VarianceBias", "TopK", "ImageFormat"] + import math import arrayfire_wrapper.lib as wrapper @@ -11,8 +15,3 @@ # Typing constants Scalar = int | float | complex | bool - -# Wrapper constants - -BinaryOperator = wrapper.BinaryOperator -Match = wrapper.Match diff --git a/arrayfire/library/input_and_output.py b/arrayfire/library/input_and_output.py new file mode 100644 index 0000000..7e22e99 --- /dev/null +++ b/arrayfire/library/input_and_output.py @@ -0,0 +1,60 @@ +__all__ = ["is_image_io_available", "read_array", "save_array"] + +from pathlib import Path +from typing import cast + +import arrayfire_wrapper.lib as wrapper +from arrayfire_wrapper.lib import is_image_io_available + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array +from arrayfire.library.constants import ImageFormat + + +@afarray_as_array +def read_array(filename: str | Path, index: None | int = None, key: None | str = None) -> Array: + if not index and not key: + raise ValueError("Can not read without specified index or key argument.") + + if index: + return cast(Array, wrapper.read_array_index(str(filename), index)) + + return cast(Array, wrapper.read_array_key(str(filename), key)) # type: ignore[arg-type] + + +def save_array(array: Array, filename: str | Path, key: str, /, *, to_append: bool = False) -> int: + return wrapper.save_array(key, array.arr, str(filename), to_append) + + +@afarray_as_array +def load_image(filename: str | Path, /, *, is_color: bool = False) -> Array: + return cast(Array, wrapper.load_image(str(filename), is_color)) + + +@afarray_as_array +def load_image_native(filename: str | Path) -> Array: + return cast(Array, wrapper.load_image_native(str(filename))) + + +@afarray_as_array +def load_image_memory(pointer: int) -> Array: + return cast(Array, wrapper.load_image_memory(pointer)) + + +def delete_image_memory(image: Array) -> None: + return wrapper.delete_image_memory(image.arr) + + +def save_image(image: Array, filename: str | Path, /) -> None: + wrapper.save_image(image.arr, str(filename)) + return None + + +def save_image_native(image: Array, filename: str | Path, /) -> None: + wrapper.save_image_native(image.arr, str(filename)) + return None + + +def save_image_memory(image: Array, pointer: int, image_format: ImageFormat) -> None: + wrapper.save_image_memory(pointer, image.arr, image_format) + return None diff --git a/arrayfire/library/interface_functions.py b/arrayfire/library/interface_functions.py new file mode 100644 index 0000000..79db4ad --- /dev/null +++ b/arrayfire/library/interface_functions.py @@ -0,0 +1,6 @@ +__all__ = ["cublas_set_math_mode", "get_native_id", "get_stream", "set_native_id"] + +from arrayfire_wrapper.lib import cublas_set_math_mode, get_native_id, get_stream, set_native_id + +# TODO +# Update and extend when opencl is added in wrapper diff --git a/arrayfire/library/linear_algebra.py b/arrayfire/library/linear_algebra.py new file mode 100644 index 0000000..9ee2de3 --- /dev/null +++ b/arrayfire/library/linear_algebra.py @@ -0,0 +1,125 @@ +__all__ = [ + "dot", + "gemm", + "matmul", + "is_lapack_available", + "cholesky", + "lu", + "qr", + "svd", + "det", + "inverse", + "norm", + "pinverse", + "rank", + "solve", +] + +from typing import cast + +import arrayfire_wrapper.lib as wrapper +from arrayfire_wrapper.lib import is_lapack_available + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array +from arrayfire.library.constants import MatProp, Norm + + +def dot( + lhs: Array, + rhs: Array, + /, + lhs_opts: MatProp = MatProp.NONE, + rhs_opts: MatProp = MatProp.NONE, + *, + return_scalar: bool = False, +) -> int | float | complex | Array: + if return_scalar: + return wrapper.dot_all(lhs.arr, rhs.arr, lhs_opts, rhs_opts) + + return Array.from_afarray(wrapper.dot(lhs.arr, rhs.arr, lhs_opts, rhs_opts)) + + +@afarray_as_array +def gemm( + lhs: Array, + rhs: Array, + /, + lhs_opts: MatProp = MatProp.NONE, + rhs_opts: MatProp = MatProp.NONE, + alpha: int | float = 1.0, + beta: int | float = 0.0, +) -> Array: + return cast(Array, wrapper.gemm(lhs.arr, rhs.arr, lhs_opts, rhs_opts, alpha, beta)) + + +@afarray_as_array +def matmul(lhs: Array, rhs: Array, /, lhs_opts: MatProp = MatProp.NONE, rhs_opts: MatProp = MatProp.NONE) -> Array: + return cast(Array, wrapper.matmul(lhs.arr, rhs.arr, lhs_opts, rhs_opts)) + + +def cholesky(array: Array, /, is_upper: bool = True, *, inplace: bool = False) -> int | tuple[Array, int]: + if inplace: + return wrapper.cholesky_inplace(array.arr, is_upper) + + matrix, info = wrapper.cholesky(array.arr, is_upper) + return Array.from_afarray(matrix), info + + +def lu(array: Array, /, *, inplace: bool = False, is_lapack_pivot: bool = True) -> Array | tuple[Array, ...]: + if inplace: + return Array.from_afarray(wrapper.lu_inplace(array.arr, is_lapack_pivot)) + + lower, upper, pivot = wrapper.lu(array.arr) + return Array.from_afarray(lower), Array.from_afarray(upper), Array.from_afarray(pivot) + + +def qr(array: Array, /, *, inplace: bool = False) -> Array | tuple[Array, ...]: + if inplace: + return Array.from_afarray(wrapper.qr_inplace(array.arr)) + + q, r, tau = wrapper.qr(array.arr) + return Array.from_afarray(q), Array.from_afarray(r), Array.from_afarray(tau) + + +def svd(array: Array, /, *, inplace: bool = False) -> tuple[Array, ...]: + if inplace: + u, s, vt, arr = wrapper.svd_inplace(array.arr) + return Array.from_afarray(u), Array.from_afarray(s), Array.from_afarray(vt), Array.from_afarray(arr) + + u, s, vt = wrapper.svd(array.arr) + return Array.from_afarray(u), Array.from_afarray(s), Array.from_afarray(vt) + + +def det(array: Array, /) -> int | float | complex: + return wrapper.det(array.arr) + + +@afarray_as_array +def inverse(array: Array, /, options: MatProp = MatProp.NONE) -> Array: + return cast(Array, wrapper.inverse(array.arr, options)) + + +def norm(array: Array, /, *, norm_type: Norm = Norm.EUCLID, p: float = 1.0, q: float = 1.0) -> float: + return wrapper.norm(array.arr, norm_type, p, q) + + +@afarray_as_array +def pinverse(array: Array, /, *, tol: float = 1e-6, options: MatProp = MatProp.NONE) -> Array: + return cast(Array, wrapper.pinverse(array.arr, tol, options)) + + +def rank(array: Array, /, *, tol: float = 1e-5) -> int: + return wrapper.rank(array.arr, tol) + + +@afarray_as_array +def solve(a: Array, b: Array, /, *, options: MatProp = MatProp.NONE, pivot: None | Array = None) -> Array: + if pivot: + return cast(Array, wrapper.solve_lu(a.arr, b.arr, pivot.arr, options)) + + return cast(Array, wrapper.solve(a.arr, b.arr, options)) + + +# TODO +# Add Sparse functions? #good_first_issue diff --git a/arrayfire/library/machine_learning.py b/arrayfire/library/machine_learning.py new file mode 100644 index 0000000..71589aa --- /dev/null +++ b/arrayfire/library/machine_learning.py @@ -0,0 +1,37 @@ +__all__ = ["convolve2_gradient_nn", "ConvGradient"] + +from typing import cast + +import arrayfire_wrapper.lib as wrapper + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array +from arrayfire.library.constants import ConvGradient + + +@afarray_as_array +def convolve2_gradient_nn( + incoming_gradient: Array, + original_signal: Array, + original_filter: Array, + convolved_output: Array, + /, + *, + strides: tuple[int, int] = (1, 1), + padding: tuple[int, int] = (0, 0), + dilation: tuple[int, int] = (1, 1), + grad_type: ConvGradient = ConvGradient.DEFAULT, +) -> Array: + return cast( + Array, + wrapper.convolve2_gradient_nn( + incoming_gradient.arr, + original_signal.arr, + original_filter.arr, + convolved_output.arr, + strides, + padding, + dilation, + grad_type, + ), + ) diff --git a/arrayfire/library/operators.py b/arrayfire/library/mathematical_functions.py old mode 100755 new mode 100644 similarity index 96% rename from arrayfire/library/operators.py rename to arrayfire/library/mathematical_functions.py index b9ec06e..c439ae0 --- a/arrayfire/library/operators.py +++ b/arrayfire/library/mathematical_functions.py @@ -1,451 +1,451 @@ -from __future__ import annotations - -from typing import cast - -import arrayfire_wrapper.lib as wrapper - -from arrayfire import Array -from arrayfire.array_object import afarray_as_array -from arrayfire.dtypes import is_complex_dtype - - -@afarray_as_array -def add(x1: Array, x2: Array, /) -> Array: - return cast(Array, wrapper.add(x1.arr, x2.arr)) - - -@afarray_as_array -def sub(x1: Array, x2: Array, /) -> Array: - return wrapper.sub(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def mul(x1: Array, x2: Array, /) -> Array: - return wrapper.mul(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def div(x1: Array, x2: Array, /) -> Array: - return wrapper.div(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def mod(x1: int | float | Array, x2: int | float | Array, /) -> Array: - """ - Calculate the modulus of two arrays or a scalar and an array. - - Parameters - ---------- - x1 : int |float |Array - The first array or scalar operand. - x2 : int |float |Array - The second array or scalar operand. - - Returns - ------- - result : Array - The array containing the modulus values after performing the operation. - - Raises - ------ - ValueError - If both operands are scalars or if the arrays' shapes do not match. - """ - - _check_operands_fit_requirements(x1, x2) - - x1_ = x1.arr if isinstance(x1, Array) else x1 - x2_ = x2.arr if isinstance(x2, Array) else x2 - - result = wrapper.mod(x1_, x2_) - return cast(Array, result) - - -@afarray_as_array -def pow(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) - - x1_ = x1.arr if isinstance(x1, Array) else x1 - x2_ = x2.arr if isinstance(x2, Array) else x2 - - return cast(Array, wrapper.pow(x1_, x2_)) - - -@afarray_as_array -def bitnot(x: Array, /) -> Array: - return wrapper.bitnot(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def bitand(x1: Array, x2: Array, /) -> Array: - return wrapper.bitand(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def bitor(x1: Array, x2: Array, /) -> Array: - return wrapper.bitor(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def bitxor(x1: Array, x2: Array, /) -> Array: - return wrapper.bitxor(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def bitshiftl(x1: Array, x2: Array, /) -> Array: - return wrapper.bitshiftl(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def bitshiftr(x1: Array, x2: Array, /) -> Array: - return wrapper.bitshiftr(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def lt(x1: Array, x2: Array, /) -> Array: - return wrapper.lt(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def le(x1: Array, x2: Array, /) -> Array: - return wrapper.le(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def gt(x1: Array, x2: Array, /) -> Array: - return wrapper.gt(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def ge(x1: Array, x2: Array, /) -> Array: - return wrapper.ge(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def eq(x1: Array, x2: Array, /) -> Array: - return wrapper.eq(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def neq(x1: Array, x2: Array, /) -> Array: - return wrapper.neq(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def clamp(x: Array, /, lo: float, hi: float) -> Array: - return NotImplemented - - -@afarray_as_array -def minof(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) - - return cast(Array, wrapper.minof(x1.arr, x2.arr)) - - -@afarray_as_array -def maxof(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) - - return wrapper.maxof(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def rem(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) - - return wrapper.rem(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def abs(x: Array, /) -> Array: - return wrapper.abs(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def arg(x: Array, /) -> Array: - return wrapper.arg(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def sign(x: Array, /) -> Array: - return wrapper.sign(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def round(x: Array, /) -> Array: - return wrapper.round(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def trunc(x: Array, /) -> Array: - return wrapper.trunc(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def floor(x: Array, /) -> Array: - return wrapper.floor(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def ceil(x: Array, /) -> Array: - return wrapper.ceil(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def hypot(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) - - return wrapper.hypot(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def sin(x: Array, /) -> Array: - _check_array_values_not_complex(x) - - return wrapper.sin(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def cos(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.cos(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def tan(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.tan(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def asin(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.asin(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def acos(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.acos(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def atan(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.atan(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def atan2(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) - return wrapper.atan2(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def cplx(x1: int | float | Array, x2: int | float | Array | None, /) -> Array: - if x2 is None: - return wrapper.cplx1(x1) # type: ignore[arg-type, return-value] - else: - return wrapper.cplx2(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def real(x: Array, /) -> Array: - return wrapper.real(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def imag(x: Array, /) -> Array: - return wrapper.imag(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def conjg(x: Array, /) -> Array: - return wrapper.conjg(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def sinh(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.sinh(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def cosh(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.cosh(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def tanh(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.tanh(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def asinh(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.asinh(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def acosh(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.acosh(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def atanh(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.atanh(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def root(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) - return wrapper.root(x1.arr, x2.arr) - - -@afarray_as_array -def pow2(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.pow2(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def sigmoid(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.sigmoid(x.arr) - - -@afarray_as_array -def exp(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.exp(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def expm1(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.expm1(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def erf(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.erf(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def erfc(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.erfc(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def log(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.log(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def log1p(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.log1p(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def log10(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.log10(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def log2(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.log2(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def sqrt(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.sqrt(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def rsqrt(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.rsqrt(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def cbrt(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.cbrt(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def factorial(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.factorial(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def tgamma(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.tgamma(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def lgamma(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.lgamma(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def iszero(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.iszero(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def isinf(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.isinf(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def isnan(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.isnan(x.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def and_(x1: Array, x2: Array, /) -> Array: - return wrapper.and_(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def or_(x1: Array, x2: Array, /) -> Array: - return wrapper.or_(x1.arr, x2.arr) # type: ignore[arg-type, return-value] - - -@afarray_as_array -def not_(x: Array, /) -> Array: - return wrapper.not_(x.arr) # type: ignore[arg-type, return-value] - - -def _check_operands_fit_requirements(x1: int | float | Array, x2: int | float | Array) -> None: - if isinstance(x1, Array) and isinstance(x2, Array): - if x1.shape != x2.shape: - raise ValueError("Array shapes must match.") - - if not isinstance(x1, Array) and not isinstance(x2, Array): - raise ValueError("At least one operand must be an Array.") - - -def _check_array_values_not_complex(x: Array) -> None: - if is_complex_dtype(x.dtype): - raise TypeError("Values of an Array should not be the complex numbers.") - pass +from __future__ import annotations + +from typing import cast + +import arrayfire_wrapper.lib as wrapper + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array +from arrayfire.dtypes import is_complex_dtype + + +@afarray_as_array +def add(x1: Array, x2: Array, /) -> Array: + return cast(Array, wrapper.add(x1.arr, x2.arr)) + + +@afarray_as_array +def sub(x1: Array, x2: Array, /) -> Array: + return wrapper.sub(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def mul(x1: Array, x2: Array, /) -> Array: + return wrapper.mul(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def div(x1: Array, x2: Array, /) -> Array: + return wrapper.div(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def mod(x1: int | float | Array, x2: int | float | Array, /) -> Array: + """ + Calculate the modulus of two arrays or a scalar and an array. + + Parameters + ---------- + x1 : int |float |Array + The first array or scalar operand. + x2 : int |float |Array + The second array or scalar operand. + + Returns + ------- + result : Array + The array containing the modulus values after performing the operation. + + Raises + ------ + ValueError + If both operands are scalars or if the arrays' shapes do not match. + """ + + _check_operands_fit_requirements(x1, x2) + + x1_ = x1.arr if isinstance(x1, Array) else x1 + x2_ = x2.arr if isinstance(x2, Array) else x2 + + result = wrapper.mod(x1_, x2_) + return cast(Array, result) + + +@afarray_as_array +def pow(x1: int | float | Array, x2: int | float | Array, /) -> Array: + _check_operands_fit_requirements(x1, x2) + + x1_ = x1.arr if isinstance(x1, Array) else x1 + x2_ = x2.arr if isinstance(x2, Array) else x2 + + return cast(Array, wrapper.pow(x1_, x2_)) + + +@afarray_as_array +def bitnot(x: Array, /) -> Array: + return wrapper.bitnot(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def bitand(x1: Array, x2: Array, /) -> Array: + return wrapper.bitand(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def bitor(x1: Array, x2: Array, /) -> Array: + return wrapper.bitor(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def bitxor(x1: Array, x2: Array, /) -> Array: + return wrapper.bitxor(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def bitshiftl(x1: Array, x2: Array, /) -> Array: + return wrapper.bitshiftl(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def bitshiftr(x1: Array, x2: Array, /) -> Array: + return wrapper.bitshiftr(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def lt(x1: Array, x2: Array, /) -> Array: + return wrapper.lt(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def le(x1: Array, x2: Array, /) -> Array: + return wrapper.le(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def gt(x1: Array, x2: Array, /) -> Array: + return wrapper.gt(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def ge(x1: Array, x2: Array, /) -> Array: + return wrapper.ge(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def eq(x1: Array, x2: Array, /) -> Array: + return wrapper.eq(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def neq(x1: Array, x2: Array, /) -> Array: + return wrapper.neq(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def clamp(x: Array, /, lo: float, hi: float) -> Array: + return NotImplemented + + +@afarray_as_array +def minof(x1: int | float | Array, x2: int | float | Array, /) -> Array: + _check_operands_fit_requirements(x1, x2) + + return cast(Array, wrapper.minof(x1.arr, x2.arr)) + + +@afarray_as_array +def maxof(x1: int | float | Array, x2: int | float | Array, /) -> Array: + _check_operands_fit_requirements(x1, x2) + + return wrapper.maxof(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def rem(x1: int | float | Array, x2: int | float | Array, /) -> Array: + _check_operands_fit_requirements(x1, x2) + + return wrapper.rem(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def abs(x: Array, /) -> Array: + return wrapper.abs(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def arg(x: Array, /) -> Array: + return wrapper.arg(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def sign(x: Array, /) -> Array: + return wrapper.sign(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def round(x: Array, /) -> Array: + return wrapper.round(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def trunc(x: Array, /) -> Array: + return wrapper.trunc(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def floor(x: Array, /) -> Array: + return wrapper.floor(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def ceil(x: Array, /) -> Array: + return wrapper.ceil(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def hypot(x1: int | float | Array, x2: int | float | Array, /) -> Array: + _check_operands_fit_requirements(x1, x2) + + return wrapper.hypot(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def sin(x: Array, /) -> Array: + _check_array_values_not_complex(x) + + return wrapper.sin(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def cos(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.cos(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def tan(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.tan(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def asin(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.asin(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def acos(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.acos(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def atan(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.atan(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def atan2(x1: int | float | Array, x2: int | float | Array, /) -> Array: + _check_operands_fit_requirements(x1, x2) + return wrapper.atan2(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def cplx(x1: int | float | Array, x2: int | float | Array | None, /) -> Array: + if x2 is None: + return wrapper.cplx1(x1) # type: ignore[arg-type, return-value] + else: + return wrapper.cplx2(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def real(x: Array, /) -> Array: + return wrapper.real(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def imag(x: Array, /) -> Array: + return wrapper.imag(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def conjg(x: Array, /) -> Array: + return wrapper.conjg(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def sinh(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.sinh(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def cosh(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.cosh(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def tanh(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.tanh(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def asinh(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.asinh(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def acosh(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.acosh(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def atanh(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.atanh(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def root(x1: int | float | Array, x2: int | float | Array, /) -> Array: + _check_operands_fit_requirements(x1, x2) + return wrapper.root(x1.arr, x2.arr) + + +@afarray_as_array +def pow2(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.pow2(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def sigmoid(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.sigmoid(x.arr) + + +@afarray_as_array +def exp(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.exp(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def expm1(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.expm1(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def erf(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.erf(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def erfc(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.erfc(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def log(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.log(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def log1p(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.log1p(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def log10(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.log10(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def log2(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.log2(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def sqrt(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.sqrt(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def rsqrt(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.rsqrt(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def cbrt(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.cbrt(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def factorial(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.factorial(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def tgamma(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.tgamma(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def lgamma(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.lgamma(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def iszero(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.iszero(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def isinf(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.isinf(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def isnan(x: Array, /) -> Array: + _check_array_values_not_complex(x) + return wrapper.isnan(x.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def and_(x1: Array, x2: Array, /) -> Array: + return wrapper.and_(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def or_(x1: Array, x2: Array, /) -> Array: + return wrapper.or_(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + + +@afarray_as_array +def not_(x: Array, /) -> Array: + return wrapper.not_(x.arr) # type: ignore[arg-type, return-value] + + +def _check_operands_fit_requirements(x1: int | float | Array, x2: int | float | Array) -> None: + if isinstance(x1, Array) and isinstance(x2, Array): + if x1.shape != x2.shape: + raise ValueError("Array shapes must match.") + + if not isinstance(x1, Array) and not isinstance(x2, Array): + raise ValueError("At least one operand must be an Array.") + + +def _check_array_values_not_complex(x: Array) -> None: + if is_complex_dtype(x.dtype): + raise TypeError("Values of an Array should not be the complex numbers.") + pass diff --git a/arrayfire/library/statistics.py b/arrayfire/library/statistics.py new file mode 100644 index 0000000..7ad70c3 --- /dev/null +++ b/arrayfire/library/statistics.py @@ -0,0 +1,72 @@ +__all__ = ["corrcoef", "cov", "mean", "median", "stdev", "topk", "var"] + +from typing import cast + +import arrayfire_wrapper.lib as wrapper + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array +from arrayfire.library.constants import TopK, VarianceBias + + +def corrcoef(x: Array, y: Array, /) -> int | float | complex: + return wrapper.corrcoef(x.arr, y.arr) + + +@afarray_as_array +def cov(x: Array, y: Array, /, *, bias: VarianceBias = VarianceBias.DEFAULT) -> Array: + return cast(Array, wrapper.cov(x.arr, y.arr, bias)) + + +def mean(x: Array, /, axis: None | int = None, *, weights: None | Array = None) -> int | float | complex | Array: + if weights: + if axis is None: + return wrapper.mean_all_weighted(x.arr, weights.arr) + + return Array.from_afarray(wrapper.mean_weighted(x.arr, weights.arr, axis)) + + if axis is None: + return wrapper.mean_all(x.arr) + + return Array.from_afarray(wrapper.mean(x.arr, axis)) + + +def median(x: Array, /, axis: None | int = None) -> int | float | complex | Array: + if axis is None: + return wrapper.median_all(x.arr) + + return Array.from_afarray(wrapper.median(x.arr, axis)) + + +def stdev( + x: Array, /, axis: None | int = None, *, bias: VarianceBias = VarianceBias.DEFAULT +) -> int | float | complex | Array: + if axis is None: + return wrapper.stdev_all(x.arr, bias) + + return Array.from_afarray(wrapper.stdev(x.arr, axis, bias)) + + +def topk(x: Array, k: int, /, *, axis: int = 0, order: TopK = TopK.DEFAULT) -> tuple[Array, Array]: + values, indices = wrapper.topk(x.arr, k, axis, order) + return Array.from_afarray(values), Array.from_afarray(indices) + + +def var( + x: Array, + /, + axis: None | int = None, + *, + weights: None | Array = None, + bias: VarianceBias = VarianceBias.DEFAULT, +) -> int | float | complex | Array: + if weights: + if axis is None: + return wrapper.var_all_weighted(x.arr, weights.arr) + + return Array.from_afarray(wrapper.var_weighted(x.arr, weights.arr, axis)) + + if axis is None: + return wrapper.var_all(x.arr, bias) + + return Array.from_afarray(wrapper.var(x.arr, axis, bias)) diff --git a/arrayfire/library/unified_api_functions.py b/arrayfire/library/unified_api_functions.py new file mode 100644 index 0000000..8922d55 --- /dev/null +++ b/arrayfire/library/unified_api_functions.py @@ -0,0 +1,23 @@ +__all__ = [ + "get_active_backend", + "get_available_backends", + "get_backend_count", + "get_backend_id", + "get_device_id", + "set_backend", +] + +from arrayfire_wrapper.lib import get_active_backend, get_available_backends, get_backend_count +from arrayfire_wrapper.lib import get_backend_id as wrapped_get_backend_id +from arrayfire_wrapper.lib import get_device_id as wrapped_get_device_id +from arrayfire_wrapper.lib import set_backend + +from arrayfire import Array + + +def get_backend_id(array: Array) -> int: + return wrapped_get_backend_id(array.arr) + + +def get_device_id(array: Array) -> int: + return wrapped_get_device_id(array.arr) diff --git a/tests/test_operators.py b/tests/test_operators.py index d5a3f02..87115af 100755 --- a/tests/test_operators.py +++ b/tests/test_operators.py @@ -1,5 +1,5 @@ from arrayfire import Array -from arrayfire.library import operators +from arrayfire.library import mathematical_functions from tests._helpers import round_to @@ -9,21 +9,21 @@ def setup_method(self) -> None: self.array2 = Array([4, 5, 6]) def test_add(self) -> None: - res = operators.add(self.array1, self.array2) + res = mathematical_functions.add(self.array1, self.array2) res_sum = self.array1 + self.array2 assert res.to_list() == res_sum.to_list() == [5, 7, 9] def test_sub(self) -> None: - res = operators.sub(self.array1, self.array2) + res = mathematical_functions.sub(self.array1, self.array2) res_sum = self.array1 - self.array2 assert res.to_list() == res_sum.to_list() == [-3, -3, -3] def test_mul(self) -> None: - res = operators.mul(self.array1, self.array2) + res = mathematical_functions.mul(self.array1, self.array2) res_product = self.array1 * self.array2 assert res.to_list() == res_product.to_list() == [4, 10, 18] def test_div(self) -> None: - res = operators.div(self.array1, self.array2) + res = mathematical_functions.div(self.array1, self.array2) res_quotient = self.array1 / self.array2 assert round_to(res.to_list()) == round_to(res_quotient.to_list()) == [0.25, 0.4, 0.5] From 452c4f62d03a521dc139b4f0882f41575ce39722 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 3 Feb 2024 06:33:36 +0200 Subject: [PATCH 08/16] Add signals and images --- README.md | 7 +- arrayfire/array_api/__init__.py | 43 - arrayfire/array_api/_array_object.py | 887 ------------------ arrayfire/array_api/_constants.py | 47 - arrayfire/array_api/_creation_function.py | 161 ---- arrayfire/array_api/_data_type_functions.py | 130 --- arrayfire/array_api/_dtypes.py | 171 ---- arrayfire/array_api/_elementwise_functions.py | 241 ----- arrayfire/array_api/_indexing_functions.py | 7 - .../array_api/_manipulation_functions.py | 43 - arrayfire/array_api/_searching_functions.py | 21 - arrayfire/array_api/_set_functions.py | 38 - arrayfire/array_api/_sorting_functions.py | 11 - arrayfire/array_api/_statistical_functions.py | 80 -- arrayfire/array_api/_utility_functions.py | 25 - arrayfire/array_api/tests/__init__.py | 0 .../tests/fixme_test_array_object.py | 177 ---- .../tests/fixme_test_elementwise_functions.py | 110 --- .../tests/test_creation_functions.py | 20 - arrayfire/library/computer_vision.py | 35 +- arrayfire/library/constants.py | 45 +- arrayfire/library/features.py | 51 + arrayfire/library/image_processing.py | 422 +++++++++ arrayfire/library/linear_algebra.py | 2 +- arrayfire/library/mathematical_functions.py | 147 +-- arrayfire/library/signal_processing.py | 335 +++++++ .../library/signal_processing/__init__.py | 0 arrayfire/library/signal_processing/fft.py | 1 - arrayfire/library/vector_algorithms.py | 2 +- 29 files changed, 949 insertions(+), 2310 deletions(-) delete mode 100755 arrayfire/array_api/__init__.py delete mode 100755 arrayfire/array_api/_array_object.py delete mode 100755 arrayfire/array_api/_constants.py delete mode 100755 arrayfire/array_api/_creation_function.py delete mode 100755 arrayfire/array_api/_data_type_functions.py delete mode 100755 arrayfire/array_api/_dtypes.py delete mode 100755 arrayfire/array_api/_elementwise_functions.py delete mode 100755 arrayfire/array_api/_indexing_functions.py delete mode 100755 arrayfire/array_api/_manipulation_functions.py delete mode 100755 arrayfire/array_api/_searching_functions.py delete mode 100755 arrayfire/array_api/_set_functions.py delete mode 100755 arrayfire/array_api/_sorting_functions.py delete mode 100755 arrayfire/array_api/_statistical_functions.py delete mode 100755 arrayfire/array_api/_utility_functions.py delete mode 100755 arrayfire/array_api/tests/__init__.py delete mode 100755 arrayfire/array_api/tests/fixme_test_array_object.py delete mode 100644 arrayfire/array_api/tests/fixme_test_elementwise_functions.py delete mode 100755 arrayfire/array_api/tests/test_creation_functions.py create mode 100644 arrayfire/library/features.py create mode 100644 arrayfire/library/image_processing.py create mode 100644 arrayfire/library/signal_processing.py delete mode 100644 arrayfire/library/signal_processing/__init__.py delete mode 100644 arrayfire/library/signal_processing/fft.py diff --git a/README.md b/README.md index 2615881..115afe9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # arrayfire-py + Arrayfire python wrapper ## Coverage @@ -7,14 +8,14 @@ Arrayfire python wrapper - [] Events - [] Functions to Create and Modify Arrays - [] Functions to Work with Internal Array Layout -- [] Image Processing - - [] Features +- [x] Image Processing + - [x] Features - [x] Input and Output Functions - [x] Interface Functions - [x] Linear Algebra - [x] Machine Learning - [x] Mathematical Functions -- [] Signal Processing +- [x] Signal Processing - [x] Statistics - [x] Unified API Functions - [x] Vector Algorithms diff --git a/arrayfire/array_api/__init__.py b/arrayfire/array_api/__init__.py deleted file mode 100755 index 1591982..0000000 --- a/arrayfire/array_api/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# flake8: noqa - -__array_api_version__ = "2022.12" - -__all__ = ["__array_api_version__"] - -from ._constants import Device - -__all__ += ["Device"] - -from ._creation_function import asarray - -__all__ += ["asarray"] - -from ._dtypes import ( - bool, - complex64, - complex128, - float32, - float64, - int8, - int16, - int32, - int64, - uint8, - uint16, - uint32, - uint64, -) - -__all__ += [ - "int8", - "int16", - "int32", - "int64", - "uint8", - "uint16", - "uint32", - "uint64", - "float32", - "float64", - "bool", -] diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py deleted file mode 100755 index ece5cf5..0000000 --- a/arrayfire/array_api/_array_object.py +++ /dev/null @@ -1,887 +0,0 @@ -from __future__ import annotations - -import types -from typing import Any, Optional, Tuple, Union - -from arrayfire import Array as AFArray -from arrayfire.array_api._constants import Device, NestedSequence, PyCapsule, SupportsBufferProtocol -from arrayfire.array_api._dtypes import ( - all_dtypes, - boolean_dtypes, - complex_floating_dtypes, - dtype_categories, - floating_dtypes, - integer_or_boolean_dtypes, - numeric_dtypes, - promote_types, -) -from arrayfire.dtypes import Dtype - - -class Array: - _array: AFArray - - def __new__(cls, *args: Any, **kwargs: Any) -> Array: - raise TypeError( - "The array_api Array object should not be instantiated directly. " - "Use an array creation function, such as asarray(), instead." - ) - - def _check_allowed_dtypes(self, other: Union[bool, int, float, Array], dtype_category: str, op: str) -> Array: - """ - Helper function for operators to only allow specific input dtypes - - Use like - - other = self._check_allowed_dtypes(other, 'numeric', '__add__') - if other is NotImplemented: - return other - """ - if self.dtype not in dtype_categories[dtype_category]: - raise TypeError(f"Only {dtype_category} dtypes are allowed in {op}") - if isinstance(other, (int, complex, float, bool)): - other = self._promote_scalar(other) - elif isinstance(other, Array): - if other.dtype not in dtype_categories[dtype_category]: - raise TypeError(f"Only {dtype_category} dtypes are allowed in {op}") - else: - return NotImplemented - - # This will raise TypeError for type combinations that are not allowed - # to promote in the spec (even if the NumPy array operator would - # promote them). - res_dtype = promote_types(self.dtype, other.dtype) - if op.startswith("__i"): - # Note: NumPy will allow in-place operators in some cases where - # the type promoted operator does not match the left-hand side - # operand. For example, - - # >>> a = np.array(1, dtype=np.int8) - # >>> a += np.array(1, dtype=np.int16) - - # The spec explicitly disallows this. - if res_dtype != self.dtype: - raise TypeError(f"Cannot perform {op} with dtypes {self.dtype} and {other.dtype}") - - return other - - def _promote_scalar(self, scalar): - """ - Returns a promoted version of a Python scalar appropriate for use with - operations on self. - - This may raise an OverflowError in cases where the scalar is an - integer that is too large to fit in a NumPy integer dtype, or - TypeError when the scalar type is incompatible with the dtype of self. - """ - # Note: Only Python scalar types that match the array dtype are - # allowed. - if isinstance(scalar, bool): - if self.dtype not in boolean_dtypes: - raise TypeError("Python bool scalars can only be promoted with bool arrays") - elif isinstance(scalar, int): - if self.dtype in boolean_dtypes: - raise TypeError("Python int scalars cannot be promoted with bool arrays") - # TODO - # if self.dtype in integer_dtypes: - # info = np.iinfo(self.dtype) - # if not (info.min <= scalar <= info.max): - # raise OverflowError( - # "Python int scalars must be within the bounds of the dtype for integer arrays") - # int + array(floating) is allowed - elif isinstance(scalar, float): - if self.dtype not in floating_dtypes: - raise TypeError("Python float scalars can only be promoted with floating-point arrays.") - elif isinstance(scalar, complex): - if self.dtype not in complex_floating_dtypes: - raise TypeError("Python complex scalars can only be promoted with complex floating-point arrays.") - else: - raise TypeError("'scalar' must be a Python scalar") - - # Note: scalars are unconditionally cast to the same dtype as the - # array. - - # Note: the spec only specifies integer-dtype/int promotion - # behavior for integers within the bounds of the integer dtype. - # Outside of those bounds we use the default NumPy behavior (either - # cast or raise OverflowError). - return Array._new(AFArray(scalar, self.dtype, shape=(1,))) - - @staticmethod - def _normalize_two_args(x1, x2) -> Tuple[Array, Array]: - # """ - # Normalize inputs to two arg functions to fix type promotion rules - - # NumPy deviates from the spec type promotion rules in cases where one - # argument is 0-dimensional and the other is not. For example: - - # >>> import numpy as np - # >>> a = np.array([1.0], dtype=np.float32) - # >>> b = np.array(1.0, dtype=np.float64) - # >>> np.add(a, b) # The spec says this should be float64 - # array([2.], dtype=float32) - - # To fix this, we add a dimension to the 0-dimension array before passing it - # through. This works because a dimension would be added anyway from - # broadcasting, so the resulting shape is the same, but this prevents NumPy - # from not promoting the dtype. - # """ - # # Another option would be to use signature=(x1.dtype, x2.dtype, None), - # # but that only works for ufuncs, so we would have to call the ufuncs - # # directly in the operator methods. One should also note that this - # # sort of trick wouldn't work for functions like searchsorted, which - # # don't do normal broadcasting, but there aren't any functions like - # # that in the array API namespace. - # if x1.ndim == 0 and x2.ndim != 0: - # # The _array[None] workaround was chosen because it is relatively - # # performant. broadcast_to(x1._array, x2.shape) is much slower. We - # # could also manually type promote x2, but that is more complicated - # # and about the same performance as this. - # x1 = Array._new(x1._array[None]) - # elif x2.ndim == 0 and x1.ndim != 0: - # x2 = Array._new(x2._array[None]) - return (x1, x2) - - @classmethod - def _new(cls, x: Union[Array, bool, int, float, complex, NestedSequence, SupportsBufferProtocol], /) -> Array: - """ - This is a private method for initializing the array API Array - object. - - Functions outside of the array_api submodule should not use this - method. Use one of the creation functions instead, such as - ``asarray``. - - """ - obj = super().__new__(cls) - # Note: The spec does not have array scalars, only 0-D arrays. - if isinstance(x, (bool, int, float, complex)): - # Convert the array scalar to a 0-D array - x = AFArray(x) # type: ignore[arg-type] - if x.dtype not in all_dtypes: # type: ignore[union-attr] - raise TypeError( - f"The array_api namespace does not support the dtype '{x.dtype}'" # type: ignore[union-attr] - ) - obj._array = x # type: ignore[assignment] - return obj - - def __str__(self: Array, /) -> str: - """ - Performs the operation __str__. - """ - return self._array.__str__() # .replace("array", "Array") - - # def __repr__(self: Array, /) -> str: - # """ - # Performs the operation __repr__. - # """ - # suffix = f", dtype={self.dtype.name})" - # if 0 in self.shape: - # prefix = "empty(" - # mid = str(self.shape) - # else: - # prefix = "Array(" - # mid = np.array2string(self._array, separator=', ', prefix=prefix, suffix=suffix) - # return prefix + mid + suffix - - def __abs__(self: Array, /) -> Array: - """ - Performs the operation __abs__. - """ - if self.dtype not in numeric_dtypes: - raise TypeError("Only numeric dtypes are allowed in __abs__") - res = self._array.__abs__() - return self.__class__._new(res) - - def __add__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __add__. - """ - other = self._check_allowed_dtypes(other, "numeric", "__add__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__add__(other._array) - return self.__class__._new(res) - - def __and__(self: Array, other: Union[int, bool, Array], /) -> Array: - """ - Performs the operation __and__. - """ - other = self._check_allowed_dtypes(other, "integer or boolean", "__and__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__and__(other._array) - return self.__class__._new(res) - - def __array_namespace__(self: Array, /, *, api_version: Optional[str] = None) -> types.ModuleType: - if api_version is not None and not api_version.startswith("2021."): - raise ValueError(f"Unrecognized array API version: {api_version!r}") - from arrayfire import array_api - - return array_api - - def __bool__(self: Array, /) -> bool: - """ - Performs the operation __bool__. - """ - # Note: This is an error here. - if self._array.ndim != 0: - raise TypeError("bool is only allowed on arrays with 0 dimensions") - res = self._array.__bool__() - return res - - def __complex__(self: Array, /) -> complex: - """ - Performs the operation __complex__. - """ - # Note: This is an error here. - if self._array.ndim != 0: - raise TypeError("complex is only allowed on arrays with 0 dimensions") - res = self._array.__complex__() - return res - - def __dlpack__(self: Array, /, *, stream: None = None) -> PyCapsule: - """ - Performs the operation __dlpack__. - """ - return self._array.__dlpack__(stream=stream) - - # def __dlpack_device__(self: Array, /) -> Tuple[IntEnum, int]: - # """ - # Performs the operation __dlpack_device__. - # """ - # # Note: device support is required for this - # return self._array.__dlpack_device__() - - def __eq__(self: Array, other: Union[int, float, bool, Array], /) -> Array: - """ - Performs the operation __eq__. - """ - # Even though "all" dtypes are allowed, we still require them to be - # promotable with each other. - other = self._check_allowed_dtypes(other, "all", "__eq__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__eq__(other._array) - return self.__class__._new(res) - - def __float__(self: Array, /) -> float: - """ - Performs the operation __float__. - """ - # Note: This is an error here. - if self._array.ndim != 0: - raise TypeError("float is only allowed on arrays with 0 dimensions") - if self.dtype in complex_floating_dtypes: - raise TypeError("float is not allowed on complex floating-point arrays") - res = self._array.__float__() - return res - - def __floordiv__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __floordiv__. - """ - other = self._check_allowed_dtypes(other, "real numeric", "__floordiv__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__floordiv__(other._array) - return self.__class__._new(res) - - def __ge__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __ge__. - """ - other = self._check_allowed_dtypes(other, "real numeric", "__ge__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__ge__(other._array) - return self.__class__._new(res) - - # def __getitem__( - # self: Array, - # key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], Array], - # /, - # ) -> Array: - # """ - # Performs the operation __getitem__. - # """ - # # Note: Only indices required by the spec are allowed. See the - # # docstring of _validate_index - # self._validate_index(key) - # if isinstance(key, Array): - # # Indexing self._array with array_api arrays can be erroneous - # key = key._array - # res = self._array.__getitem__(key) - # return self._new(res) - - def __gt__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __gt__. - """ - other = self._check_allowed_dtypes(other, "real numeric", "__gt__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__gt__(other._array) - return self.__class__._new(res) - - def __int__(self: Array, /) -> int: - """ - Performs the operation __int__. - """ - # Note: This is an error here. - if self._array.ndim != 0: - raise TypeError("int is only allowed on arrays with 0 dimensions") - if self.dtype in complex_floating_dtypes: - raise TypeError("int is not allowed on complex floating-point arrays") - res = self._array.__int__() - return res - - def __index__(self: Array, /) -> int: - """ - Performs the operation __index__. - """ - res = self._array.__index__() - return res - - def __invert__(self: Array, /) -> Array: - """ - Performs the operation __invert__. - """ - if self.dtype not in integer_or_boolean_dtypes: - raise TypeError("Only integer or boolean dtypes are allowed in __invert__") - res = self._array.__invert__() - return self.__class__._new(res) - - def __le__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __le__. - """ - other = self._check_allowed_dtypes(other, "real numeric", "__le__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__le__(other._array) - return self.__class__._new(res) - - def __lshift__(self: Array, other: Union[int, Array], /) -> Array: - """ - Performs the operation __lshift__. - """ - other = self._check_allowed_dtypes(other, "integer", "__lshift__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__lshift__(other._array) - return self.__class__._new(res) - - def __lt__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __lt__. - """ - other = self._check_allowed_dtypes(other, "real numeric", "__lt__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__lt__(other._array) - return self.__class__._new(res) - - def __matmul__(self: Array, other: Array, /) -> Array: - """ - Performs the operation __matmul__. - """ - # matmul is not defined for scalars, but without this, we may get - # the wrong error message from asarray. - other = self._check_allowed_dtypes(other, "numeric", "__matmul__") - if other is NotImplemented: - return other - res = self._array.__matmul__(other._array) - return self.__class__._new(res) - - def __mod__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __mod__. - """ - other = self._check_allowed_dtypes(other, "real numeric", "__mod__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__mod__(other._array) - return self.__class__._new(res) - - def __mul__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __mul__. - """ - other = self._check_allowed_dtypes(other, "numeric", "__mul__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__mul__(other._array) - return self.__class__._new(res) - - def __ne__(self: Array, other: Union[int, float, bool, Array], /) -> Array: - """ - Performs the operation __ne__. - """ - other = self._check_allowed_dtypes(other, "all", "__ne__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__ne__(other._array) - return self.__class__._new(res) - - def __neg__(self: Array, /) -> Array: - """ - Performs the operation __neg__. - """ - if self.dtype not in numeric_dtypes: - raise TypeError("Only numeric dtypes are allowed in __neg__") - res = self._array.__neg__() - return self.__class__._new(res) - - def __or__(self: Array, other: Union[int, bool, Array], /) -> Array: - """ - Performs the operation __or__. - """ - other = self._check_allowed_dtypes(other, "integer or boolean", "__or__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__or__(other._array) - return self.__class__._new(res) - - def __pos__(self: Array, /) -> Array: - """ - Performs the operation __pos__. - """ - if self.dtype not in numeric_dtypes: - raise TypeError("Only numeric dtypes are allowed in __pos__") - res = self._array.__pos__() - return self.__class__._new(res) - - # def __pow__(self: Array, other: Union[int, float, Array], /) -> Array: - # """ - # Performs the operation __pow__. - # """ - # from ._elementwise_functions import pow - - # other = self._check_allowed_dtypes(other, "numeric", "__pow__") - # if other is NotImplemented: - # return other - # # Note: NumPy's __pow__ does not follow type promotion rules for 0-d - # # arrays, so we use pow() here instead. - # return pow(self, other) - - def __rshift__(self: Array, other: Union[int, Array], /) -> Array: - """ - Performs the operation __rshift__. - """ - other = self._check_allowed_dtypes(other, "integer", "__rshift__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__rshift__(other._array) - return self.__class__._new(res) - - # def __setitem__( - # self, - # key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], Array], - # value: Union[int, float, bool, Array], - # /, - # ) -> None: - # """ - # Performs the operation __setitem__. - # """ - # # Note: Only indices required by the spec are allowed. See the - # # docstring of _validate_index - # self._validate_index(key) - # if isinstance(key, Array): - # # Indexing self._array with array_api arrays can be erroneous - # key = key._array - # self._array.__setitem__(key, asarray(value)._array) - - def __sub__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __sub__. - """ - other = self._check_allowed_dtypes(other, "numeric", "__sub__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__sub__(other._array) - return self.__class__._new(res) - - # PEP 484 requires int to be a subtype of float, but __truediv__ should - # not accept int. - def __truediv__(self: Array, other: Union[float, Array], /) -> Array: - """ - Performs the operation __truediv__. - """ - other = self._check_allowed_dtypes(other, "floating-point", "__truediv__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__truediv__(other._array) - return self.__class__._new(res) - - def __xor__(self: Array, other: Union[int, bool, Array], /) -> Array: - """ - Performs the operation __xor__. - """ - other = self._check_allowed_dtypes(other, "integer or boolean", "__xor__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__xor__(other._array) - return self.__class__._new(res) - - def __iadd__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __iadd__. - """ - other = self._check_allowed_dtypes(other, "numeric", "__iadd__") - if other is NotImplemented: - return other - self._array.__iadd__(other._array) - return self - - def __radd__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __radd__. - """ - other = self._check_allowed_dtypes(other, "numeric", "__radd__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__radd__(other._array) - return self.__class__._new(res) - - def __iand__(self: Array, other: Union[int, bool, Array], /) -> Array: - """ - Performs the operation __iand__. - """ - other = self._check_allowed_dtypes(other, "integer or boolean", "__iand__") - if other is NotImplemented: - return other - self._array.__iand__(other._array) - return self - - def __rand__(self: Array, other: Union[int, bool, Array], /) -> Array: - """ - Performs the operation __rand__. - """ - other = self._check_allowed_dtypes(other, "integer or boolean", "__rand__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__rand__(other._array) - return self.__class__._new(res) - - def __ifloordiv__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __ifloordiv__. - """ - other = self._check_allowed_dtypes(other, "real numeric", "__ifloordiv__") - if other is NotImplemented: - return other - self._array.__ifloordiv__(other._array) - return self - - def __rfloordiv__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __rfloordiv__. - """ - other = self._check_allowed_dtypes(other, "real numeric", "__rfloordiv__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__rfloordiv__(other._array) - return self.__class__._new(res) - - def __ilshift__(self: Array, other: Union[int, Array], /) -> Array: - """ - Performs the operation __ilshift__. - """ - other = self._check_allowed_dtypes(other, "integer", "__ilshift__") - if other is NotImplemented: - return other - self._array.__ilshift__(other._array) - return self - - def __rlshift__(self: Array, other: Union[int, Array], /) -> Array: - """ - Performs the operation __rlshift__. - """ - other = self._check_allowed_dtypes(other, "integer", "__rlshift__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__rlshift__(other._array) - return self.__class__._new(res) - - def __imatmul__(self: Array, other: Array, /) -> Array: - """ - Performs the operation __imatmul__. - """ - # matmul is not defined for scalars, but without this, we may get - # the wrong error message from asarray. - other = self._check_allowed_dtypes(other, "numeric", "__imatmul__") - if other is NotImplemented: - return other - res = self._array.__imatmul__(other._array) - return self.__class__._new(res) - - def __rmatmul__(self: Array, other: Array, /) -> Array: - """ - Performs the operation __rmatmul__. - """ - # matmul is not defined for scalars, but without this, we may get - # the wrong error message from asarray. - other = self._check_allowed_dtypes(other, "numeric", "__rmatmul__") - if other is NotImplemented: - return other - res = self._array.__rmatmul__(other._array) - return self.__class__._new(res) - - def __imod__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __imod__. - """ - other = self._check_allowed_dtypes(other, "real numeric", "__imod__") - if other is NotImplemented: - return other - self._array.__imod__(other._array) - return self - - def __rmod__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __rmod__. - """ - other = self._check_allowed_dtypes(other, "real numeric", "__rmod__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__rmod__(other._array) - return self.__class__._new(res) - - def __imul__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __imul__. - """ - other = self._check_allowed_dtypes(other, "numeric", "__imul__") - if other is NotImplemented: - return other - self._array.__imul__(other._array) - return self - - def __rmul__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __rmul__. - """ - other = self._check_allowed_dtypes(other, "numeric", "__rmul__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__rmul__(other._array) - return self.__class__._new(res) - - def __ior__(self: Array, other: Union[int, bool, Array], /) -> Array: - """ - Performs the operation __ior__. - """ - other = self._check_allowed_dtypes(other, "integer or boolean", "__ior__") - if other is NotImplemented: - return other - self._array.__ior__(other._array) - return self - - def __ror__(self: Array, other: Union[int, bool, Array], /) -> Array: - """ - Performs the operation __ror__. - """ - other = self._check_allowed_dtypes(other, "integer or boolean", "__ror__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__ror__(other._array) - return self.__class__._new(res) - - def __ipow__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __ipow__. - """ - other = self._check_allowed_dtypes(other, "numeric", "__ipow__") - if other is NotImplemented: - return other - self._array.__ipow__(other._array) - return self - - def __rpow__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __rpow__. - """ - other = self._check_allowed_dtypes(other, "numeric", "__rpow__") - if other is NotImplemented: - return other - self._array.__rpow__(other._array) - return self - - def __irshift__(self: Array, other: Union[int, Array], /) -> Array: - """ - Performs the operation __irshift__. - """ - other = self._check_allowed_dtypes(other, "integer", "__irshift__") - if other is NotImplemented: - return other - self._array.__irshift__(other._array) - return self - - def __rrshift__(self: Array, other: Union[int, Array], /) -> Array: - """ - Performs the operation __rrshift__. - """ - other = self._check_allowed_dtypes(other, "integer", "__rrshift__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__rrshift__(other._array) - return self.__class__._new(res) - - def __isub__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __isub__. - """ - other = self._check_allowed_dtypes(other, "numeric", "__isub__") - if other is NotImplemented: - return other - self._array.__isub__(other._array) - return self - - def __rsub__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __rsub__. - """ - other = self._check_allowed_dtypes(other, "numeric", "__rsub__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__rsub__(other._array) - return self.__class__._new(res) - - def __itruediv__(self: Array, other: Union[float, Array], /) -> Array: - """ - Performs the operation __itruediv__. - """ - other = self._check_allowed_dtypes(other, "floating-point", "__itruediv__") - if other is NotImplemented: - return other - self._array.__itruediv__(other._array) - return self - - def __rtruediv__(self: Array, other: Union[float, Array], /) -> Array: - """ - Performs the operation __rtruediv__. - """ - other = self._check_allowed_dtypes(other, "floating-point", "__rtruediv__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__rtruediv__(other._array) - return self.__class__._new(res) - - def __ixor__(self: Array, other: Union[int, bool, Array], /) -> Array: - """ - Performs the operation __ixor__. - """ - other = self._check_allowed_dtypes(other, "integer or boolean", "__ixor__") - if other is NotImplemented: - return other - self._array.__ixor__(other._array) - return self - - def __rxor__(self: Array, other: Union[int, bool, Array], /) -> Array: - """ - Performs the operation __rxor__. - """ - other = self._check_allowed_dtypes(other, "integer or boolean", "__rxor__") - if other is NotImplemented: - return other - self, other = self._normalize_two_args(self, other) - res = self._array.__rxor__(other._array) - return self.__class__._new(res) - - def to_device(self: Array, device: Device, /, stream: None = None) -> Array: - if stream is not None: - raise ValueError("The stream argument to to_device() is not supported") - if device == "cpu": - return self - raise ValueError(f"Unsupported device {device!r}") - - @property - def dtype(self) -> Dtype: - """ - Array API compatible wrapper for :py:meth:`np.ndarray.dtype `. - - See its docstring for more information. - """ - return self._array.dtype - - # @property - # def device(self) -> Device: - # return "cpu" - - # @property - # def mT(self) -> Array: - # from .linalg import matrix_transpose - - # return matrix_transpose(self) - - @property - def ndim(self) -> int: - """ - Array API compatible wrapper for :py:meth:`np.ndarray.ndim `. - - See its docstring for more information. - """ - return self._array.ndim - - @property - def shape(self) -> Tuple[int, ...]: - """ - Array API compatible wrapper for :py:meth:`np.ndarray.shape `. - - See its docstring for more information. - """ - return self._array.shape - - @property - def size(self) -> int: - """ - Array API compatible wrapper for :py:meth:`np.ndarray.size `. - - See its docstring for more information. - """ - return self._array.size - - @property - def T(self) -> Array: - """ - Array API compatible wrapper for :py:meth:`np.ndarray.T `. - - See its docstring for more information. - """ - # Note: T only works on 2-dimensional arrays. See the corresponding - # note in the specification: - # https://data-apis.org/array-api/latest/API_specification/array_object.html#t - if self.ndim != 2: - raise ValueError( - "x.T requires x to have 2 dimensions. " - "Use x.mT to transpose stacks of matrices and permute_dims() to permute dimensions." - ) - return self.__class__._new(self._array.T) diff --git a/arrayfire/array_api/_constants.py b/arrayfire/array_api/_constants.py deleted file mode 100755 index 8f89829..0000000 --- a/arrayfire/array_api/_constants.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -This file defines the types for type annotations. - -These names aren't part of the module namespace, but they are used in the -annotations in the function signatures. The functions in the module are only -valid for inputs that match the given type annotations. -""" - -from __future__ import annotations - -from enum import Enum - -__all__ = [ - "Device", - "SupportsDLPack", - "SupportsBufferProtocol", - "PyCapsule", -] - -from typing import Any, Iterator, Protocol, TypeVar - -_T_co = TypeVar("_T_co", covariant=True) - - -class NestedSequence(Protocol[_T_co]): - def __getitem__(self, key: int, /) -> _T_co | NestedSequence[_T_co]: - ... - - def __len__(self, /) -> int: - ... - - -class Device(Enum): - cpu = "cpu" - gpu = "gpu" - - def __iter__(self) -> Iterator[Device]: - yield self - - -SupportsBufferProtocol = Any -PyCapsule = Any - - -class SupportsDLPack(Protocol): - def __dlpack__(self, /, *, stream: None = ...) -> PyCapsule: - ... diff --git a/arrayfire/array_api/_creation_function.py b/arrayfire/array_api/_creation_function.py deleted file mode 100755 index 845c9a1..0000000 --- a/arrayfire/array_api/_creation_function.py +++ /dev/null @@ -1,161 +0,0 @@ -from __future__ import annotations - -from typing import List, Optional, Tuple, Union - -from arrayfire import Array as AFArray -from arrayfire.array_api._array_object import Array -from arrayfire.array_api._constants import Device, NestedSequence, SupportsBufferProtocol -from arrayfire.array_api._dtypes import all_dtypes -from arrayfire.dtypes import Dtype -from arrayfire.library.device import PointerSource - - -def _check_valid_dtype(dtype: Optional[Dtype]) -> None: - # Note: Only spelling dtypes as the dtype objects is supported. - - # We use this instead of "dtype in _all_dtypes" because the dtype objects - # define equality with the sorts of things we want to disallow. - for d in (None,) + all_dtypes: - if dtype is d: - return - raise ValueError("dtype must be one of the supported dtypes") - - -def asarray( - obj: Union[Array, bool, int, float, complex, NestedSequence, SupportsBufferProtocol], - /, - *, - dtype: Optional[Dtype] = None, - device: Optional[Device] = None, - copy: Optional[bool] = None, -) -> Array: - _check_valid_dtype(dtype) - - # if device not in supported_devices: - # raise ValueError(f"Unsupported device {device!r}") - - if dtype is None and isinstance(obj, int) and (obj > 2**64 or obj < -(2**63)): - raise OverflowError("Integer out of bounds for array dtypes") - - if device == Device.cpu or device is None: - to_device = False - elif device == Device.gpu: - to_device = True - else: - raise ValueError(f"Unsupported device {device!r}") - - # if isinstance(obj, int | float): - # afarray = AFArray([obj], dtype=dtype, shape=(1,), to_device=to_device) - # return Array._new(afarray) - - afarray = AFArray(obj, dtype=dtype, to_device=to_device) - return Array._new(afarray) - - -def arange( - start: Union[int, float], - /, - stop: Optional[Union[int, float]] = None, - step: Union[int, float] = 1, - *, - dtype: Optional[Dtype] = None, - device: Optional[Device] = None, -) -> Array: - return NotImplemented - - -def empty( - shape: Union[int, Tuple[int, ...]], - *, - dtype: Optional[Dtype] = None, - device: Optional[Device] = None, -) -> Array: - return NotImplemented - - -def empty_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: - return NotImplemented - - -def eye( - n_rows: int, - n_cols: Optional[int] = None, - /, - *, - k: int = 0, - dtype: Optional[Dtype] = None, - device: Optional[Device] = None, -) -> Array: - return NotImplemented - - -def full( - shape: Union[int, Tuple[int, ...]], - fill_value: Union[int, float], - *, - dtype: Optional[Dtype] = None, - device: Optional[Device] = None, -) -> Array: - return NotImplemented - - -def full_like( - x: Array, - /, - fill_value: Union[int, float], - *, - dtype: Optional[Dtype] = None, - device: Optional[Device] = None, -) -> Array: - return NotImplemented - - -def linspace( - start: Union[int, float], - stop: Union[int, float], - /, - num: int, - *, - dtype: Optional[Dtype] = None, - device: Optional[Device] = None, - endpoint: bool = True, -) -> Array: - return NotImplemented - - -def meshgrid(*arrays: Array, indexing: str = "xy") -> List[Array]: - return NotImplemented - - -def ones( - shape: Union[int, Tuple[int, ...]], - *, - dtype: Optional[Dtype] = None, - device: Optional[Device] = None, -) -> Array: - return NotImplemented - - -def ones_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: - return NotImplemented - - -def tril(x: Array, /, *, k: int = 0) -> Array: - return NotImplemented - - -def triu(x: Array, /, *, k: int = 0) -> Array: - return NotImplemented - - -def zeros( - shape: Union[int, Tuple[int, ...]], - *, - dtype: Optional[Dtype] = None, - device: Optional[Device] = None, -) -> Array: - return NotImplemented - - -def zeros_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: - return NotImplemented diff --git a/arrayfire/array_api/_data_type_functions.py b/arrayfire/array_api/_data_type_functions.py deleted file mode 100755 index 3f22d87..0000000 --- a/arrayfire/array_api/_data_type_functions.py +++ /dev/null @@ -1,130 +0,0 @@ -from dataclasses import dataclass -from typing import List, Tuple, Union - -from arrayfire import Array as AFArray -from arrayfire.array_api._array_object import Array -from arrayfire.array_api._dtypes import all_dtypes, promote_types -from arrayfire.dtypes import Dtype - - -def astype(x: Array, dtype: Dtype, /, *, copy: bool = True) -> Array: - return NotImplemented - - -def broadcast_arrays(*arrays: Array) -> List[Array]: - return NotImplemented - - -def broadcast_to(x: Array, /, shape: Tuple[int, ...]) -> Array: - return NotImplemented - - -def can_cast(from_: Union[Dtype, Array], to: Dtype, /) -> bool: - return NotImplemented - - -@dataclass -class finfo_object: - bits: int - eps: float - max: float - min: float - smallest_normal: float - dtype: Dtype - - -# TODO fix and add complex support -# float32 : finfo(32, 1.19209290 * 10^-7, 3.4028234 * 10^38, -3.4028234 * 10^38, 1.1754943 *10^−38, float32) -# float64 : finfo(64, 2.2204460492503131*10^-16, 1.7976931348623157 · 10^308, -1.7976931348623157 · 10^308, 2.2250738585072014 * 10^−308 , float64) -# float16 : finfo(16, 0.00097656, 65504, -65504, 0.00006103515625, float16) - - -# TODO separate API supported dtypes, add aliases -# Common -# int16 = Dtype("int16", "h", ctypes.c_short, "short int", 10) == s16 -# int32 = Dtype("int32", "i", ctypes.c_int, "int", 5) == s32 -# int64 = Dtype("int64", "l", ctypes.c_longlong, "long int", 8) == s64 -# uint8 = Dtype("uint8", "B", ctypes.c_ubyte, "unsigned_char", 7) == u8 -# uint16 = Dtype("uint16", "H", ctypes.c_ushort, "unsigned short int", 11) == u16 -# uint32 = Dtype("uint32", "I", ctypes.c_uint, "unsigned int", 6) == u32 -# uint64 = Dtype("uint64", "L", ctypes.c_ulonglong, "unsigned long int", 9) == u64 -# float16 = Dtype("float16", "e", ctypes.c_uint16, "half", 12) == f16 -# float32 = Dtype("float32", "f", ctypes.c_float, "float", 0) == f32 -# float64 = Dtype("float64", "d", ctypes.c_double, "double", 2) == f64 -# bool = Dtype("bool", "b", ctypes.c_char, "bool", 4) == b8 - -# AF API -# complex32 = Dtype("complex32", "F", ctypes.c_float * 2, "float complex", 1) == c32 -# complex64 = Dtype("complex64", "D", ctypes.c_double * 2, "double complex", 3) == c64 - -# Array API -# int8 = Dtype("int8", "b8", ctypes.c_char, "int8", 4) # HACK int8 - not supported in AF -> same as b8 -# complex64 = Dtype("complex64", "F", ctypes.c_float * 2, "float complex", 1) # type: ignore[arg-type] -# complex128 = Dtype("complex128", "D", ctypes.c_double * 2, "double complex", 3) # type: ignore[arg-type] - - -@dataclass -class iinfo_object: - bits: int - max: int - min: int - dtype: Dtype - - -def finfo(type: Union[Dtype, Array], /) -> finfo_object: - return NotImplemented - - -# TODO fix bug -# def iinfo(type: Union[Dtype, Array], /) -> iinfo_object: -# # Reference: https://en.cppreference.com/w/cpp/language/types - -# if isinstance(type, Dtype): -# type_ = type -# elif isinstance(type, Array): -# type_ = Array.dtype -# else: -# raise ValueError("Wrong type.") - -# match type_: -# case int32: -# return iinfo_object(32, 2147483648, -2147483647, int32) -# case int16: -# return iinfo_object(16,32767, -32768, int16) -# case int8: -# return iinfo_object(8, 127, -128, int8) -# case int64: -# return iinfo_object(64, 9223372036854775807, -9223372036854775808, int64) - - -def isdtype(dtype: Dtype, kind: Union[Dtype, str, Tuple[Union[Dtype, str], ...]]) -> bool: - return NotImplemented - - -def result_type(*arrays_and_dtypes: Union[Array, Dtype]) -> Dtype: - """ - Array API compatible wrapper for :py:func:`np.result_type `. - - See its docstring for more information. - """ - # Note: we use a custom implementation that gives only the type promotions - # required by the spec rather than using np.result_type. NumPy implements - # too many extra type promotions like int64 + uint64 -> float64, and does - # value-based casting on scalar arrays. - A = [] - for a in arrays_and_dtypes: - if isinstance(a, Array): - a = a.dtype - elif isinstance(a, AFArray) or a not in all_dtypes: - raise TypeError("result_type() inputs must be array_api arrays or dtypes") - A.append(a) - - if len(A) == 0: - raise ValueError("at least one array or dtype is required") - elif len(A) == 1: - return A[0] - else: - t = A[0] - for t2 in A[1:]: - t = promote_types(t, t2) - return t diff --git a/arrayfire/array_api/_dtypes.py b/arrayfire/array_api/_dtypes.py deleted file mode 100755 index 7903b56..0000000 --- a/arrayfire/array_api/_dtypes.py +++ /dev/null @@ -1,171 +0,0 @@ -from __future__ import annotations - -import ctypes - -__all__ = [ - "all_dtypes", - "boolean_dtypes", - "real_floating_dtypes", - "floating_dtypes", - "complex_floating_dtypes", - "integer_dtypes", - "signed_integer_dtypes", - "unsigned_integer_dtypes", - "integer_or_boolean_dtypes", - "real_numeric_dtypes", - "numeric_dtypes", - "dtype_categories", - # OG - "bool", - "complex64", - "complex128", - "float32", - "float64", - "int8", - "int16", - "int32", - "int64", - "uint8", - "uint16", - "uint32", - "uint64", -] - - -from arrayfire import bool -from arrayfire import complex32 as afcomplex32 -from arrayfire import complex64 as afcomplex64 -from arrayfire import float32, float64, int16, int32, int64, uint8, uint16, uint32, uint64 -from arrayfire.dtypes import Dtype - -int8 = Dtype("int8", "b8", ctypes.c_char, "int8", 4) # HACK int8 - not supported in AF -> same as b8 -complex64 = afcomplex32 -complex128 = afcomplex64 - -all_dtypes = ( - int8, - int16, - int32, - int64, - uint8, - uint16, - uint32, - uint64, - float32, - float64, - complex64, - complex128, - bool, -) -boolean_dtypes = (bool,) -real_floating_dtypes = (float32, float64) -floating_dtypes = (float32, float64, complex64, complex128) -complex_floating_dtypes = (complex64, complex128) -integer_dtypes = (int8, int16, int32, int64, uint8, uint16, uint32, uint64) -signed_integer_dtypes = (int8, int16, int32, int64) -unsigned_integer_dtypes = (uint8, uint16, uint32, uint64) -integer_or_boolean_dtypes = boolean_dtypes + integer_dtypes -real_numeric_dtypes = real_floating_dtypes + integer_dtypes -numeric_dtypes = floating_dtypes + integer_dtypes - -dtype_categories = { - "all": all_dtypes, - "real numeric": real_numeric_dtypes, - "numeric": numeric_dtypes, - "integer": integer_dtypes, - "integer or boolean": integer_or_boolean_dtypes, - "boolean": boolean_dtypes, - "real floating-point": floating_dtypes, - "complex floating-point": complex_floating_dtypes, - "floating-point": floating_dtypes, -} - - -# Note: the spec defines a restricted type promotion table compared to NumPy. -# In particular, cross-kind promotions like integer + float or boolean + -# integer are not allowed, even for functions that accept both kinds. -# Additionally, NumPy promotes signed integer + uint64 to float64, but this -# promotion is not allowed here. To be clear, Python scalar int objects are -# allowed to promote to floating-point dtypes, but only in array operators -# (see Array._promote_scalar) method in _array_object.py. -_promotion_table = { - (int8, int8): int8, - (int8, int16): int16, - (int8, int32): int32, - (int8, int64): int64, - (int16, int8): int16, - (int16, int16): int16, - (int16, int32): int32, - (int16, int64): int64, - (int32, int8): int32, - (int32, int16): int32, - (int32, int32): int32, - (int32, int64): int64, - (int64, int8): int64, - (int64, int16): int64, - (int64, int32): int64, - (int64, int64): int64, - (uint8, uint8): uint8, - (uint8, uint16): uint16, - (uint8, uint32): uint32, - (uint8, uint64): uint64, - (uint16, uint8): uint16, - (uint16, uint16): uint16, - (uint16, uint32): uint32, - (uint16, uint64): uint64, - (uint32, uint8): uint32, - (uint32, uint16): uint32, - (uint32, uint32): uint32, - (uint32, uint64): uint64, - (uint64, uint8): uint64, - (uint64, uint16): uint64, - (uint64, uint32): uint64, - (uint64, uint64): uint64, - (int8, uint8): int16, - (int8, uint16): int32, - (int8, uint32): int64, - (int16, uint8): int16, - (int16, uint16): int32, - (int16, uint32): int64, - (int32, uint8): int32, - (int32, uint16): int32, - (int32, uint32): int64, - (int64, uint8): int64, - (int64, uint16): int64, - (int64, uint32): int64, - (uint8, int8): int16, - (uint16, int8): int32, - (uint32, int8): int64, - (uint8, int16): int16, - (uint16, int16): int32, - (uint32, int16): int64, - (uint8, int32): int32, - (uint16, int32): int32, - (uint32, int32): int64, - (uint8, int64): int64, - (uint16, int64): int64, - (uint32, int64): int64, - (float32, float32): float32, - (float32, float64): float64, - (float64, float32): float64, - (float64, float64): float64, - (complex64, complex64): complex64, - (complex64, complex128): complex128, - (complex128, complex64): complex128, - (complex128, complex128): complex128, - (float32, complex64): complex64, - (float32, complex128): complex128, - (float64, complex64): complex128, - (float64, complex128): complex128, - (complex64, float32): complex64, - (complex64, float64): complex128, - (complex128, float32): complex128, - (complex128, float64): complex128, - (bool, bool): bool, -} - - -def promote_types(type1: Dtype, type2: Dtype) -> Dtype: - if (type1, type2) in _promotion_table: - return _promotion_table[type1, type2] - raise TypeError(f"{type1} and {type2} cannot be type promoted together") diff --git a/arrayfire/array_api/_elementwise_functions.py b/arrayfire/array_api/_elementwise_functions.py deleted file mode 100755 index d981d1f..0000000 --- a/arrayfire/array_api/_elementwise_functions.py +++ /dev/null @@ -1,241 +0,0 @@ -from __future__ import annotations - -from arrayfire.library import mathematical_functions - -from ._array_object import Array - - -def abs(x: Array, /) -> Array: - return Array._new(mathematical_functions.abs(x._array.arr)) - - -def acos(x: Array, /) -> Array: - return Array._new(mathematical_functions.acos(x._array.arr)) - - -def acosh(x: Array, /) -> Array: - return Array._new(mathematical_functions.acosh(x._array.arr)) - - -def add(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.add(x1._array.arr, x2._array.arr)) - - -def asin(x: Array, /) -> Array: - return Array._new(mathematical_functions.asin(x._array.arr)) - - -def asinh(x: Array, /) -> Array: - return Array._new(mathematical_functions.asinh(x._array.arr)) - - -def atan(x: Array, /) -> Array: - return Array._new(mathematical_functions.atan(x._array.arr)) - - -def atan2(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.atan2(x1._array.arr, x2._array.arr)) - - -def atanh(x: Array, /) -> Array: - return Array._new(mathematical_functions.atanh(x._array.arr)) - - -def bitwise_and(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.bitand(x1._array.arr, x2._array.arr)) - - -def bitwise_left_shift(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.bitshiftl(x1._array.arr, x2._array.arr)) - - -def bitwise_invert(x: Array, /) -> Array: - return Array._new(mathematical_functions.bitnot(x._array.arr)) - - -def bitwise_or(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.bitor(x1._array.arr, x2._array.arr)) - - -def bitwise_right_shift(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.bitshiftr(x1._array.arr, x2._array.arr)) - - -def bitwise_xor(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.bitxor(x1._array.arr, x2._array.arr)) - - -def ceil(x: Array, /) -> Array: - return Array._new(mathematical_functions.ceil(x._array.arr)) - - -def conj(x: Array, /) -> Array: - return Array._new(mathematical_functions.conjg(x._array.arr)) - - -def cos(x: Array, /) -> Array: - return Array._new(mathematical_functions.cos(x._array.arr)) - - -def cosh(x: Array, /) -> Array: - return Array._new(mathematical_functions.cosh(x._array.arr)) - - -def divide(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.div(x1._array.arr, x2._array.arr)) - - -def equal(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.eq(x1._array.arr, x2._array.arr)) - - -def exp(x: Array, /) -> Array: - return Array._new(mathematical_functions.exp(x._array.arr)) - - -def expm1(x: Array, /) -> Array: - return Array._new(mathematical_functions.expm1(x._array.arr)) - - -def floor(x: Array, /) -> Array: - return Array._new(mathematical_functions.floor(x._array.arr)) - - -def floor_divide(x1: Array, x2: Array, /) -> Array: - return NotImplemented - - -def greater(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.gt(x1._array.arr, x2._array.arr)) - - -def greater_equal(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.ge(x1._array.arr, x2._array.arr)) - - -def imag(x: Array, /) -> Array: - return Array._new(mathematical_functions.imag(x._array.arr)) - - -def isfinite(x: Array, /) -> Array: - return NotImplemented - - -def isinf(x: Array, /) -> Array: - return Array._new(mathematical_functions.isinf(x._array.arr)) - - -def isnan(x: Array, /) -> Array: - return Array._new(mathematical_functions.isnan(x._array.arr)) - - -def less(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.lt(x1._array.arr, x2._array.arr)) - - -def less_equal(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.le(x1._array.arr, x2._array.arr)) - - -def log(x: Array, /) -> Array: - return Array._new(mathematical_functions.log(x._array.arr)) - - -def log1p(x: Array, /) -> Array: - return Array._new(mathematical_functions.log1p(x._array.arr)) - - -def log2(x: Array, /) -> Array: - return Array._new(mathematical_functions.log2(x._array.arr)) - - -def log10(x: Array, /) -> Array: - return Array._new(mathematical_functions.log10(x._array.arr)) - - -def logaddexp(x1: Array, x2: Array) -> Array: - return NotImplemented - - -def logical_and(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.land(x1._array.arr, x2._array.arr)) - - -def logical_not(x: Array, /) -> Array: - return Array._new(mathematical_functions.lnot(x._array.arr)) - - -def logical_or(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.lor(x1._array.arr, x2._array.arr)) - - -def logical_xor(x1: Array, x2: Array, /) -> Array: - return NotImplemented - - -def multiply(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.mul(x1._array.arr, x2._array.arr)) - - -def negative(x: Array, /) -> Array: - return NotImplemented - - -def not_equal(x1: Array, x2: Array, /) -> Array: - return NotImplemented - - -def positive(x: Array, /) -> Array: - return NotImplemented - - -def pow(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.pow(x1._array.arr, x2._array.arr)) - - -def real(x: Array, /) -> Array: - return Array._new(mathematical_functions.real(x._array.arr)) - - -def remainder(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.rem(x1._array.arr, x2._array.arr)) - - -def round(x: Array, /) -> Array: - return Array._new(mathematical_functions.round(x._array.arr)) - - -def sign(x: Array, /) -> Array: - return Array._new(mathematical_functions.sign(x._array.arr)) - - -def sin(x: Array, /) -> Array: - return Array._new(mathematical_functions.sin(x._array.arr)) - - -def sinh(x: Array, /) -> Array: - return Array._new(mathematical_functions.sinh(x._array.arr)) - - -def square(x: Array, /) -> Array: - return NotImplemented - - -def sqrt(x: Array, /) -> Array: - return Array._new(mathematical_functions.sqrt(x._array.arr)) - - -def subtract(x1: Array, x2: Array, /) -> Array: - return Array._new(mathematical_functions.sub(x1._array.arr, x2._array.arr)) - - -def tan(x: Array, /) -> Array: - return Array._new(mathematical_functions.tan(x._array.arr)) - - -def tanh(x: Array, /) -> Array: - return Array._new(mathematical_functions.tanh(x._array.arr)) - - -def trunc(x: Array, /) -> Array: - return Array._new(mathematical_functions.trunc(x._array.arr)) diff --git a/arrayfire/array_api/_indexing_functions.py b/arrayfire/array_api/_indexing_functions.py deleted file mode 100755 index 0b19766..0000000 --- a/arrayfire/array_api/_indexing_functions.py +++ /dev/null @@ -1,7 +0,0 @@ -from typing import Optional - -from ._array_object import Array - - -def take(x: Array, indices: Array, /, *, axis: Optional[int] = None) -> Array: - return NotImplemented diff --git a/arrayfire/array_api/_manipulation_functions.py b/arrayfire/array_api/_manipulation_functions.py deleted file mode 100755 index eebafd4..0000000 --- a/arrayfire/array_api/_manipulation_functions.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -from typing import List, Optional, Tuple, Union - -from ._array_object import Array - - -def concat(arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: Optional[int] = 0) -> Array: - return NotImplemented - - -def expand_dims(x: Array, /, *, axis: int) -> Array: - return NotImplemented - - -def flip(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Array: - return NotImplemented - - -def permute_dims(x: Array, /, axes: Tuple[int, ...]) -> Array: - return NotImplemented - - -def reshape(x: Array, /, shape: Tuple[int, ...], *, copy: Optional[bool] = None) -> Array: - return NotImplemented - - -def roll( - x: Array, - /, - shift: Union[int, Tuple[int, ...]], - *, - axis: Optional[Union[int, Tuple[int, ...]]] = None, -) -> Array: - return NotImplemented - - -def squeeze(x: Array, /, axis: Union[int, Tuple[int, ...]]) -> Array: - return NotImplemented - - -def stack(arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: int = 0) -> Array: - return NotImplemented diff --git a/arrayfire/array_api/_searching_functions.py b/arrayfire/array_api/_searching_functions.py deleted file mode 100755 index 1ef4438..0000000 --- a/arrayfire/array_api/_searching_functions.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -from typing import Optional, Tuple - -from ._array_object import Array - - -def argmax(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) -> Array: - return NotImplemented - - -def argmin(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) -> Array: - return NotImplemented - - -def nonzero(x: Array, /) -> Tuple[Array, ...]: - return NotImplemented - - -def where(condition: Array, x1: Array, x2: Array, /) -> Array: - return NotImplemented diff --git a/arrayfire/array_api/_set_functions.py b/arrayfire/array_api/_set_functions.py deleted file mode 100755 index ea09497..0000000 --- a/arrayfire/array_api/_set_functions.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import annotations - -from typing import NamedTuple - -from ._array_object import Array - - -class UniqueAllResult(NamedTuple): - values: Array - indices: Array - inverse_indices: Array - counts: Array - - -class UniqueCountsResult(NamedTuple): - values: Array - counts: Array - - -class UniqueInverseResult(NamedTuple): - values: Array - inverse_indices: Array - - -def unique_all(x: Array, /) -> UniqueAllResult: - return NotImplemented - - -def unique_counts(x: Array, /) -> UniqueCountsResult: - return NotImplemented - - -def unique_inverse(x: Array, /) -> UniqueInverseResult: - return NotImplemented - - -def unique_values(x: Array, /) -> Array: - return NotImplemented diff --git a/arrayfire/array_api/_sorting_functions.py b/arrayfire/array_api/_sorting_functions.py deleted file mode 100755 index 478b781..0000000 --- a/arrayfire/array_api/_sorting_functions.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from ._array_object import Array - - -def argsort(x: Array, /, *, axis: int = -1, descending: bool = False, stable: bool = True) -> Array: - return NotImplemented - - -def sort(x: Array, /, *, axis: int = -1, descending: bool = False, stable: bool = True) -> Array: - return NotImplemented diff --git a/arrayfire/array_api/_statistical_functions.py b/arrayfire/array_api/_statistical_functions.py deleted file mode 100755 index 1e47b4b..0000000 --- a/arrayfire/array_api/_statistical_functions.py +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -from typing import Optional, Tuple, Union - -from ._array_object import Array -from ._dtypes import Dtype - - -def max( - x: Array, - /, - *, - axis: Optional[Union[int, Tuple[int, ...]]] = None, - keepdims: bool = False, -) -> Array: - return NotImplemented - - -def mean( - x: Array, - /, - *, - axis: Optional[Union[int, Tuple[int, ...]]] = None, - keepdims: bool = False, -) -> Array: - return NotImplemented - - -def min( - x: Array, - /, - *, - axis: Optional[Union[int, Tuple[int, ...]]] = None, - keepdims: bool = False, -) -> Array: - return NotImplemented - - -def prod( - x: Array, - /, - *, - axis: Optional[Union[int, Tuple[int, ...]]] = None, - dtype: Optional[Dtype] = None, - keepdims: bool = False, -) -> Array: - return NotImplemented - - -def std( - x: Array, - /, - *, - axis: Optional[Union[int, Tuple[int, ...]]] = None, - correction: Union[int, float] = 0.0, - keepdims: bool = False, -) -> Array: - return NotImplemented - - -def sum( - x: Array, - /, - *, - axis: Optional[Union[int, Tuple[int, ...]]] = None, - dtype: Optional[Dtype] = None, - keepdims: bool = False, -) -> Array: - return NotImplemented - - -def var( - x: Array, - /, - *, - axis: Optional[Union[int, Tuple[int, ...]]] = None, - correction: Union[int, float] = 0.0, - keepdims: bool = False, -) -> Array: - return NotImplemented diff --git a/arrayfire/array_api/_utility_functions.py b/arrayfire/array_api/_utility_functions.py deleted file mode 100755 index 1ca4248..0000000 --- a/arrayfire/array_api/_utility_functions.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import annotations - -from typing import Optional, Tuple, Union - -from ._array_object import Array - - -def all( - x: Array, - /, - *, - axis: Optional[Union[int, Tuple[int, ...]]] = None, - keepdims: bool = False, -) -> Array: - return NotImplemented - - -def any( - x: Array, - /, - *, - axis: Optional[Union[int, Tuple[int, ...]]] = None, - keepdims: bool = False, -) -> Array: - return NotImplemented diff --git a/arrayfire/array_api/tests/__init__.py b/arrayfire/array_api/tests/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/arrayfire/array_api/tests/fixme_test_array_object.py b/arrayfire/array_api/tests/fixme_test_array_object.py deleted file mode 100755 index 876e457..0000000 --- a/arrayfire/array_api/tests/fixme_test_array_object.py +++ /dev/null @@ -1,177 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Iterator - -import pytest - -from arrayfire import bool, int8, int16, int32, int64, uint64 -from arrayfire.array_api._creation_function import asarray -from arrayfire.array_api._data_type_functions import result_type -from arrayfire.array_api._dtypes import ( - boolean_dtypes, - complex_floating_dtypes, - floating_dtypes, - integer_dtypes, - integer_or_boolean_dtypes, - numeric_dtypes, - real_floating_dtypes, - real_numeric_dtypes, -) - -if TYPE_CHECKING: - from arrayfire.array_api._array_object import Array - - -def test_operators() -> None: - # For every operator, we test that it works for the required type - # combinations and raises TypeError otherwise - binary_op_dtypes = { - "__add__": "numeric", - "__and__": "integer_or_boolean", - "__eq__": "all", - "__floordiv__": "real numeric", - "__ge__": "real numeric", - "__gt__": "real numeric", - "__le__": "real numeric", - "__lshift__": "integer", - "__lt__": "real numeric", - "__mod__": "real numeric", - "__mul__": "numeric", - "__ne__": "all", - "__or__": "integer_or_boolean", - "__pow__": "numeric", - "__rshift__": "integer", - "__sub__": "numeric", - "__truediv__": "floating", - "__xor__": "integer_or_boolean", - } - - # Recompute each time because of in-place ops - def _array_vals() -> Iterator[Array]: - for d in integer_dtypes: - yield asarray(1, dtype=d) - for d in boolean_dtypes: - yield asarray(False, dtype=d) - for d in floating_dtypes: - yield asarray(1.0, dtype=d) - - BIG_INT = int(1e30) - for op, dtypes in binary_op_dtypes.items(): - ops = [op] - if op not in ["__eq__", "__ne__", "__le__", "__ge__", "__lt__", "__gt__"]: - rop = "__r" + op[2:] - iop = "__i" + op[2:] - ops += [rop, iop] - for s in [1, 1.0, 1j, BIG_INT, False]: - for _op in ops: - for a in _array_vals(): - # Test array op scalar. From the spec, the following combinations - # are supported: - - # - Python bool for a bool array dtype, - # - a Python int within the bounds of the given dtype for integer array dtypes, - # - a Python int or float for real floating-point array dtypes - # - a Python int, float, or complex for complex floating-point array dtypes - - if ( - ( - dtypes == "all" - or dtypes == "numeric" - and a.dtype in numeric_dtypes - or dtypes == "real numeric" - and a.dtype in real_numeric_dtypes - or dtypes == "integer" - and a.dtype in integer_dtypes - or dtypes == "integer_or_boolean" - and a.dtype in integer_or_boolean_dtypes - or dtypes == "boolean" - and a.dtype in boolean_dtypes - or dtypes == "floating" - and a.dtype in floating_dtypes - ) - # bool is a subtype of int, which is why we avoid - # isinstance here. - and ( - a.dtype in boolean_dtypes - and type(s) == bool - or a.dtype in integer_dtypes - and type(s) == int - or a.dtype in real_floating_dtypes - and type(s) in [float, int] - or a.dtype in complex_floating_dtypes - and type(s) in [complex, float, int] - ) - ): - if a.dtype in integer_dtypes and s == BIG_INT: - pytest.raises(OverflowError, lambda: getattr(a, _op)(s)) - else: - # ignore warnings from pow(BIG_INT) - pytest.raises(RuntimeWarning, getattr(a, _op)(s)) - getattr(a, _op)(s) - else: - pytest.raises(TypeError, lambda: getattr(a, _op)(s)) - - # Test array op array. - for _op in ops: - for x in _array_vals(): - for y in _array_vals(): - # See the promotion table in NEP 47 or the array - # API spec page on type promotion. Mixed kind - # promotion is not defined. - if ( - x.dtype == uint64 - and y.dtype in [int8, int16, int32, int64] - or y.dtype == uint64 - and x.dtype in [int8, int16, int32, int64] - or x.dtype in integer_dtypes - and y.dtype not in integer_dtypes - or y.dtype in integer_dtypes - and x.dtype not in integer_dtypes - or x.dtype in boolean_dtypes - and y.dtype not in boolean_dtypes - or y.dtype in boolean_dtypes - and x.dtype not in boolean_dtypes - or x.dtype in floating_dtypes - and y.dtype not in floating_dtypes - or y.dtype in floating_dtypes - and x.dtype not in floating_dtypes - ): - pytest.raises(TypeError, lambda: getattr(x, _op)(y)) - # Ensure in-place operators only promote to the same dtype as the left operand. - elif _op.startswith("__i") and result_type(x.dtype, y.dtype) != x.dtype: - pytest.raises(TypeError, lambda: getattr(x, _op)(y)) - # Ensure only those dtypes that are required for every operator are allowed. - elif ( - dtypes == "all" - and ( - x.dtype in boolean_dtypes - and y.dtype in boolean_dtypes - or x.dtype in numeric_dtypes - and y.dtype in numeric_dtypes - ) - or ( - dtypes == "real numeric" - and x.dtype in real_numeric_dtypes - and y.dtype in real_numeric_dtypes - ) - or (dtypes == "numeric" and x.dtype in numeric_dtypes and y.dtype in numeric_dtypes) - or dtypes == "integer" - and x.dtype in integer_dtypes - and y.dtype in integer_dtypes - or dtypes == "integer_or_boolean" - and ( - x.dtype in integer_dtypes - and y.dtype in integer_dtypes - or x.dtype in boolean_dtypes - and y.dtype in boolean_dtypes - ) - or dtypes == "boolean" - and x.dtype in boolean_dtypes - and y.dtype in boolean_dtypes - or dtypes == "floating" - and x.dtype in floating_dtypes - and y.dtype in floating_dtypes - ): - getattr(x, _op)(y) - else: - pytest.raises(TypeError, lambda: getattr(x, _op)(y)) diff --git a/arrayfire/array_api/tests/fixme_test_elementwise_functions.py b/arrayfire/array_api/tests/fixme_test_elementwise_functions.py deleted file mode 100644 index 853bee6..0000000 --- a/arrayfire/array_api/tests/fixme_test_elementwise_functions.py +++ /dev/null @@ -1,110 +0,0 @@ -from inspect import getfullargspec -from typing import TYPE_CHECKING, Callable, Iterator - -import pytest - -from .. import _elementwise_functions, asarray -from .._array_object import Array -from .._dtypes import boolean_dtypes, dtype_categories, floating_dtypes, int8, integer_dtypes, real_floating_dtypes -from .._elementwise_functions import bitwise_left_shift, bitwise_right_shift - - -def nargs(func: Callable) -> int: - return len(getfullargspec(func).args) - - -def test_function_types() -> None: - # Test that every function accepts only the required input types. We only - # test the negative cases here (error). The positive cases are tested in - # the array API test suite. - - elementwise_function_input_types = { - "abs": "numeric", - "acos": "floating-point", - "acosh": "floating-point", - "add": "numeric", - "asin": "floating-point", - "asinh": "floating-point", - "atan": "floating-point", - "atan2": "real floating-point", - "atanh": "floating-point", - # "bitwise_and": "integer or boolean", - # "bitwise_invert": "integer or boolean", - # "bitwise_left_shift": "integer", - # "bitwise_or": "integer or boolean", - # "bitwise_right_shift": "integer", - # "bitwise_xor": "integer or boolean", - "ceil": "real numeric", - # "conj": "complex floating-point", - "cos": "floating-point", - "cosh": "floating-point", - "divide": "floating-point", - "equal": "all", - "exp": "floating-point", - "expm1": "floating-point", - "floor": "real numeric", - "floor_divide": "real numeric", - "greater": "real numeric", - "greater_equal": "real numeric", - # "imag": "complex floating-point", - "isfinite": "numeric", - "isinf": "numeric", - "isnan": "numeric", - "less": "real numeric", - "less_equal": "real numeric", - "log": "floating-point", - "logaddexp": "real floating-point", - "log10": "floating-point", - "log1p": "floating-point", - "log2": "floating-point", - # "logical_and": "boolean", - # "logical_not": "boolean", - # "logical_or": "boolean", - # "logical_xor": "boolean", - "multiply": "numeric", - "negative": "numeric", - "not_equal": "all", - "positive": "numeric", - "pow": "numeric", - # "real": "complex floating-point", - "remainder": "real numeric", - "round": "numeric", - "sign": "numeric", - "sin": "floating-point", - "sinh": "floating-point", - "sqrt": "floating-point", - "square": "numeric", - "subtract": "numeric", - "tan": "floating-point", - "tanh": "floating-point", - "trunc": "real numeric", - } - - def _array_vals() -> Iterator[Array]: - for dt in integer_dtypes: - if dt in {int8}: - continue - yield asarray([1], dtype=dt) - # for d in boolean_dtypes: - # yield asarray(False, dtype=d) - for dt in real_floating_dtypes: - yield asarray([1.0], dtype=dt) - - for x in _array_vals(): - for func_name, types in elementwise_function_input_types.items(): - dtypes = dtype_categories[types] - func = getattr(_elementwise_functions, func_name) - if nargs(func) == 2: - for y in _array_vals(): - if x.dtype not in dtypes or y.dtype not in dtypes: - pytest.raises(TypeError, lambda: func(x, y)) - else: - if x.dtype not in dtypes: - print(func) - pytest.raises(TypeError, lambda: func(x)) - - -# def test_bitwise_shift_error() -> None: -# # bitwise shift functions should raise when the second argument is negative -# pytest.raises(ValueError, lambda: bitwise_left_shift(asarray([1, 1]), asarray([1, -1]))) -# pytest.raises(ValueError, lambda: bitwise_right_shift(asarray([1, 1]), asarray([1, -1]))) diff --git a/arrayfire/array_api/tests/test_creation_functions.py b/arrayfire/array_api/tests/test_creation_functions.py deleted file mode 100755 index 33e3c07..0000000 --- a/arrayfire/array_api/tests/test_creation_functions.py +++ /dev/null @@ -1,20 +0,0 @@ -import pytest - -from arrayfire.array_api import asarray -from arrayfire.array_api._array_object import Array -from arrayfire.array_api._constants import Device -from arrayfire.dtypes import float16 - - -def test_asarray_errors() -> None: - # Test various protections against incorrect usage - pytest.raises(TypeError, lambda: Array([1])) - pytest.raises(TypeError, lambda: asarray(["a"])) - pytest.raises(ValueError, lambda: asarray([1.0], dtype=float16)) - pytest.raises(OverflowError, lambda: asarray(2**100)) - # pytest.raises(OverflowError, lambda: asarray([2**100])) # FIXME - asarray([1], device=Device.cpu) # Doesn't error - pytest.raises(ValueError, lambda: asarray([1], device="gpu")) # type: ignore[arg-type] - - pytest.raises(ValueError, lambda: asarray([1], dtype=int)) # type: ignore[arg-type] - pytest.raises(ValueError, lambda: asarray([1], dtype="i")) # type: ignore[arg-type] diff --git a/arrayfire/library/computer_vision.py b/arrayfire/library/computer_vision.py index 0650166..14dab87 100644 --- a/arrayfire/library/computer_vision.py +++ b/arrayfire/library/computer_vision.py @@ -2,9 +2,9 @@ import arrayfire_wrapper.lib as wrapper -from arrayfire import Array -from arrayfire.array_object import afarray_as_array +from arrayfire.array_object import Array, afarray_as_array from arrayfire.library.constants import Match +from arrayfire.library.features import Features def gloh( @@ -17,7 +17,7 @@ def gloh( dobule_input: bool = True, intensity_scale: float = 1.0 / 255, feature_ratio: float = 0.05, -) -> tuple[Array, Array]: +) -> tuple[Features, Array]: features, descriptors = wrapper.gloh( image.arr, n_layers, @@ -28,7 +28,7 @@ def gloh( intensity_scale, feature_ratio, ) - return Array.from_afarray(features), Array.from_afarray(descriptors) + return Features.from_affeatures(features), Array.from_afarray(descriptors) def orb( @@ -39,9 +39,9 @@ def orb( scale_factor: float = 1.5, n_levels: int = 4, blur_image: bool = False, -) -> tuple[Array, Array]: +) -> tuple[Features, Array]: features, descriptors = wrapper.orb(image.arr, fast_threshold, max_features, scale_factor, n_levels, blur_image) - return Array.from_afarray(features), Array.from_afarray(descriptors) + return Features.from_affeatures(features), Array.from_afarray(descriptors) def sift( @@ -54,7 +54,7 @@ def sift( dobule_input: bool = True, intensity_scale: float = 1.0 / 255, feature_ratio: float = 0.05, -) -> tuple[Array, Array]: +) -> tuple[Features, Array]: features, descriptors = wrapper.sift( image.arr, n_layers, @@ -65,7 +65,7 @@ def sift( intensity_scale, feature_ratio, ) - return Array.from_afarray(features), Array.from_afarray(descriptors) + return Features.from_affeatures(features), Array.from_afarray(descriptors) @afarray_as_array @@ -73,7 +73,6 @@ def dog(image: Array, radius1: int, radius2: int, /) -> Array: return cast(Array, wrapper.dog(image.arr, radius1, radius2)) -@afarray_as_array def fast( image: Array, /, @@ -82,11 +81,10 @@ def fast( non_max: bool = True, feature_ratio: float = 0.05, edge: int = 3, -) -> Array: - return cast(Array, wrapper.fast(image.arr, fast_threshold, arc_length, non_max, feature_ratio, edge)) +) -> Features: + return Features.from_affeatures(wrapper.fast(image.arr, fast_threshold, arc_length, non_max, feature_ratio, edge)) -@afarray_as_array def harris( image: Array, /, @@ -95,11 +93,12 @@ def harris( sigma: float = 1.0, block_size: int = 0, k_threshold: float = 0, -) -> Array: - return cast(Array, wrapper.harris(image.arr, max_corners, min_response, sigma, block_size, k_threshold)) +) -> Features: + return Features.from_affeatures( + wrapper.harris(image.arr, max_corners, min_response, sigma, block_size, k_threshold) + ) -@afarray_as_array def susan( image: Array, /, @@ -108,8 +107,10 @@ def susan( geom_threshold: float = 1.0, feature_ratio: float = 0.05, edge: int = 3, -) -> Array: - return cast(Array, wrapper.susan(image.arr, radius, diff_threshold, geom_threshold, feature_ratio, edge)) +) -> Features: + return Features.from_affeatures( + wrapper.susan(image.arr, radius, diff_threshold, geom_threshold, feature_ratio, edge) + ) def hamming_matcher(query: Array, train: Array, /, axis: int = 0, n_nearest: int = 1) -> tuple[Array, Array]: diff --git a/arrayfire/library/constants.py b/arrayfire/library/constants.py index 6bfe47d..23361c0 100644 --- a/arrayfire/library/constants.py +++ b/arrayfire/library/constants.py @@ -1,8 +1,43 @@ -__all__ = ["pi"] +# flake8: noqa -from arrayfire_wrapper.lib import BinaryOperator, ConvGradient, ImageFormat, Match, MatProp, Norm, TopK, VarianceBias +__all__ = ["pi"] -__all__ += ["Match", "MatProp", "BinaryOperator", "Norm", "ConvGradient", "VarianceBias", "TopK", "ImageFormat"] +from arrayfire_wrapper.lib import ( + BinaryOperator, + CannyThreshold, + Connectivity, + ConvDomain, + ConvGradient, + ConvMode, + CSpace, + Diffusion, + Flux, + ImageFormat, + Interp, + IterativeDeconv, + Match, + MatProp, + Norm, + Pad, + TopK, + VarianceBias, + YCCStd, +) + +__all__ += [ + "Match", + "MatProp", + "BinaryOperator", + "Norm", + "ConvGradient", + "VarianceBias", + "TopK", + "ImageFormat", + "CSpace", + "YCCStd", + "Flux", + "Diffusion", +] import math @@ -11,7 +46,3 @@ import arrayfire as af pi = wrapper.constant(math.pi, (1,), af.float64) - -# Typing constants - -Scalar = int | float | complex | bool diff --git a/arrayfire/library/features.py b/arrayfire/library/features.py new file mode 100644 index 0000000..732ce06 --- /dev/null +++ b/arrayfire/library/features.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +from typing import cast + +import arrayfire_wrapper.lib as wrapper + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array + + +class Features: + def __init__(self, max_features: int | None = None) -> None: + self._pointer = ( + wrapper.create_features(max_features) if max_features else wrapper.AFFeatures.create_null_pointer() + ) + + def __del__(self) -> None: + if self._pointer.value == 0: + return + + wrapper.release_features(self._pointer) + + @classmethod + def from_affeatures(cls, features: wrapper.AFFeatures) -> Features: + out = cls() + out._pointer = features + return out + + @property + @afarray_as_array + def x(self) -> Array: + return cast(Array, wrapper.get_features_xpos(self._pointer)) + + @property + @afarray_as_array + def y(self) -> Array: + return cast(Array, wrapper.get_features_ypos(self._pointer)) + + @property + def num_features(self) -> int: + return wrapper.get_features_num(self._pointer) + + @property + @afarray_as_array + def score(self) -> Array: + return cast(Array, wrapper.get_features_score(self._pointer)) + + @property + @afarray_as_array + def size(self) -> Array: + return cast(Array, wrapper.get_features_size(self._pointer)) diff --git a/arrayfire/library/image_processing.py b/arrayfire/library/image_processing.py new file mode 100644 index 0000000..5b9afc1 --- /dev/null +++ b/arrayfire/library/image_processing.py @@ -0,0 +1,422 @@ +__all__ = [ + "color_space", + "gray2rgb", + "hsv2rgb", + "rgb2gray", + "rgb2hsv", + "rgb2ycbcr", + "anisotropic_diffusion", + "bilateral", + "canny", + "inverse_deconv", + "iterative_deconv", + "maxfilt", + "mean_shift", + "medfilt", + "medfilt1", + "medfilt2", + "minfilt", + "sat", + "sobel_operator", + "gaussian_kernel", + "hist_equal", + "histogram", + "resize", + "rotate", + "scale", + "skew", + "transform", + "transform_coordinates", + "translate", + "confidence_cc", + "regions", + "dilate", + "erode", + "wrap", + "unwrap", +] + + +from typing import cast + +import arrayfire_wrapper.lib as wrapper +from dtypes import Dtype, float32 + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array +from arrayfire.library.constants import ( + CannyThreshold, + Connectivity, + CSpace, + Diffusion, + Flux, + Interp, + IterativeDeconv, + Pad, + YCCStd, +) + +from .data import constant +from .vector_algorithms import max as afmax +from .vector_algorithms import min as afmin + +# Colorspace conversions + + +@afarray_as_array +def color_space(image: Array, to_type: CSpace, from_type: CSpace, /) -> Array: + return cast(Array, wrapper.color_space(image.arr, to_type, from_type)) + + +@afarray_as_array +def gray2rgb(image: Array, /, *, r_factor: float = 1.0, g_factor: float = 1.0, b_factor: float = 1.0) -> Array: + return cast(Array, wrapper.gray2rgb(image.arr, r_factor, g_factor, b_factor)) + + +@afarray_as_array +def hsv2rgb(image: Array, /) -> Array: + return cast(Array, wrapper.hsv2rgb(image.arr)) + + +@afarray_as_array +def rgb2gray( + image: Array, /, *, r_factor: float = 0.2126, g_factor: float = 0.7152, b_factor: float = 0.0722 +) -> Array: + return cast(Array, wrapper.rgb2gray(image.arr, r_factor, g_factor, b_factor)) + + +@afarray_as_array +def rgb2hsv(image: Array, /) -> Array: + return cast(Array, wrapper.rgb2hsv(image.arr)) + + +@afarray_as_array +def rgb2ycbcr(image: Array, /, *, standard: YCCStd = YCCStd.YCC_601) -> Array: + return cast(Array, wrapper.rgb2ycbcr(image.arr, standard)) + + +@afarray_as_array +def ycbcr2rgb(image: Array, /, *, standard: YCCStd.YCC_601) -> Array: + return cast(Array, wrapper.ycbcr2rgb(image.arr, standard)) + + +# Filters + + +@afarray_as_array +def anisotropic_diffusion( + image: Array, + timestep: float, + conductance: float, + iterations: int, + /, + *, + fftype: Flux = Flux.QUADRATIC, + diffusion_kind: Diffusion = Diffusion.GRAD, +) -> Array: + return cast( + Array, wrapper.anisotropic_diffusion(image.arr, timestep, conductance, iterations, fftype, diffusion_kind) + ) + + +@afarray_as_array +def bilateral(image: Array, spatial_sigma: float, chromatic_sigma: float, /, *, is_color: bool = False) -> Array: + return cast(Array, wrapper.bilateral(image.arr, spatial_sigma, chromatic_sigma, is_color)) + + +@afarray_as_array +def canny( + image: Array, + low_threshold: float, + high_threshold: float, + /, + *, + threshold_type: CannyThreshold = CannyThreshold.MANUAL, + sobel_window: int = 3, + is_fast: bool = False, +) -> Array: + return cast(Array, wrapper.canny(image.arr, threshold_type, low_threshold, high_threshold, sobel_window, is_fast)) + + +@afarray_as_array +def inverse_deconv( + image: Array, psf: Array, gamma: float, /, *, algo: IterativeDeconv = IterativeDeconv.DEFAULT +) -> Array: + return cast(Array, wrapper.inverse_deconv(image.arr, psf.arr, gamma, algo)) + + +@afarray_as_array +def iterative_deconv( + image: Array, + psf: Array, + iterations: int, + relax_factor: float, + /, + *, + algo: IterativeDeconv = IterativeDeconv.DEFAULT, +) -> Array: + return cast(Array, wrapper.iterative_deconv(image.arr, psf.arr, iterations, relax_factor, algo)) + + +@afarray_as_array +def maxfilt(image: Array, /, *, wind_length: int = 3, wind_width: int = 3, edge_pad: Pad = Pad.ZERO) -> Array: + return cast(Array, wrapper.maxfilt(image.arr, wind_length, wind_width, edge_pad)) + + +@afarray_as_array +def mean_shift( + image: Array, spatial_sigma: float, chromatic_sigma: float, iterations: int, /, *, is_color: bool = False +) -> Array: + return cast(Array, wrapper.mean_shift(image.arr, spatial_sigma, chromatic_sigma, iterations, is_color)) + + +@afarray_as_array +def medfilt(image: Array, /, *, wind_length: int = 3, wind_width: int = 3, edge_pad: Pad = Pad.ZERO) -> Array: + return cast(Array, wrapper.medfilt(image.arr, wind_length, wind_width, edge_pad)) + + +@afarray_as_array +def medfilt1(image: Array, /, *, wind_width: int = 3, edge_pad: Pad = Pad.ZERO) -> Array: + return cast(Array, wrapper.medfilt1(image.arr, wind_width, edge_pad)) + + +@afarray_as_array +def medfilt2(image: Array, /, *, wind_length: int = 3, wind_width: int = 3, edge_pad: Pad = Pad.ZERO) -> Array: + return cast(Array, wrapper.medfilt2(image.arr, wind_length, wind_width, edge_pad)) + + +@afarray_as_array +def minfilt(image: Array, /, *, wind_length: int = 3, wind_width: int = 3, edge_pad: Pad = Pad.ZERO) -> Array: + return cast(Array, wrapper.minfilt(image.arr, wind_length, wind_width, edge_pad)) + + +@afarray_as_array +def sat(image: Array, /) -> Array: + return cast(Array, wrapper.sat(image.arr)) + + +def sobel_operator(image: Array, /, *, kernel_size: int = 3) -> tuple[Array, Array]: + dx, dy = wrapper.sobel_operator(image.arr, kernel_size) + return Array.from_afarray(dx), Array.from_afarray(dy) + + +# Gaussian kernel + + +def gaussian_kernel( + rows: int, columns: int, /, *, rows_sigma: None | float = None, columns_sigma: None | float = None +) -> Array: + rs = 0.25 * rows + 0.75 if not rows_sigma else rows_sigma + cs = 0.25 * columns + 0.75 if not columns_sigma else columns_sigma + + return cast(Array, wrapper.gaussian_kernel(rows, columns, rs, cs)) + + +# Histograms + + +@afarray_as_array +def hist_equal(image: Array, histogram: Array, /) -> Array: + return cast(Array, wrapper.hist_equal(image.arr, histogram.arr)) + + +@afarray_as_array +def histogram( + image: Array, n_bins: int, /, *, min_value: None | float = None, max_value: None | float = None +) -> Array: + min_v = afmin(image) if not min_value else min_value + max_v = afmax(image) if not max_value else max_value + + return cast(Array, wrapper.histogram(image.arr, n_bins, min_v, max_v)) # type: ignore[arg-type] + + +# Image transformation + + +@afarray_as_array +def resize(image: Array, size_scale: float | tuple[int, int], /, *, method: Interp = Interp.NEAREST) -> Array: + if isinstance(size_scale, float): + dims = (int(size_scale * image.shape[0]), int(size_scale * image.shape[1])) + elif isinstance(size_scale, tuple): + dims = size_scale + else: + raise TypeError( + "`size_scale` should be specified either as scale of original image, or the specific output size." + ) + + return cast(Array, wrapper.resize(image.arr, dims[0], dims[1], method)) + + +@afarray_as_array +def rotate(image: Array, theta: float, /, *, is_crop: bool = True, method: Interp = Interp.NEAREST) -> Array: + return cast(Array, wrapper.rotate(image.arr, theta, is_crop, method)) + + +@afarray_as_array +def scale( + image: Array, + scale: tuple[float, float], + /, + *, + output_size: tuple[int, int] = (0, 0), + method: Interp = Interp.NEAREST, +) -> Array: + return cast(Array, wrapper.scale(image.arr, scale[0], scale[1], output_size[0], output_size[1], method)) + + +@afarray_as_array +def skew( + image: Array, + skew: tuple[float, float], + /, + *, + output_size: tuple[int, int] = (0, 0), + method: Interp = Interp.NEAREST, + is_inverse: bool = True, +) -> Array: + return cast(Array, wrapper.skew(image.arr, skew[0], skew[1], output_size[0], output_size[1], method, is_inverse)) + + +@afarray_as_array +def transform( + image: Array, + transform_matrix: Array, + /, + *, + output_size: tuple[int, int] = (0, 0), + method: Interp = Interp.NEAREST, + is_inverse: bool = True, +) -> Array: + return cast( + Array, wrapper.transform(image.arr, transform_matrix.arr, output_size[0], output_size[1], method, is_inverse) + ) + + +@afarray_as_array +def transform_coordinates(image: Array, coordinates: tuple[float, float], /) -> Array: + return cast(Array, wrapper.transform_coordinates(image.arr, coordinates[0], coordinates[1])) + + +@afarray_as_array +def translate( + image: Array, translation: tuple[float, float], /, *, output_size: tuple[int, int], method: Interp = Interp.NEAREST +) -> Array: + return cast( + Array, wrapper.translate(image.arr, translation[0], translation[1], output_size[0], output_size[1], method) + ) + + +# Labeling + + +@afarray_as_array +def confidence_cc( + image: Array, + seed: tuple[Array, Array], + radius: int, + multiplier: int, + iterations: int, + segmented_value: float, + /, +) -> Array: + return cast( + Array, + wrapper.confidence_cc(image.arr, seed[0].arr, seed[1].arr, radius, multiplier, iterations, segmented_value), + ) + + +@afarray_as_array +def regions( + image: Array, /, *, connectivity: Connectivity = Connectivity.FOUR, output_dtype: Dtype = float32 +) -> Array: + return cast(Array, wrapper.regions(image.arr, connectivity, output_dtype)) + + +# Morphological operations + + +# TODO +# Split back to two separate functions +def dilate(image: Array, /, *, mask: Array | None = None) -> Array: + if image.ndim == 2: + mask_ = constant(1, (3, 3), float32) if not mask else mask + return Array.from_afarray(wrapper.dilate(image.arr, mask_.arr)) + + if image.ndim == 3: + mask_ = constant(1, (3, 3, 3), float32) if not mask else mask + return Array.from_afarray(wrapper.dilate3(image.arr, mask_.arr)) + + raise ValueError("Image should be either 2D or 3D.") + + +# TODO +# Split back to two separate functions +def erode(image: Array, /, *, mask: Array | None = None) -> Array: + if image.ndim == 2: + mask_ = constant(1, (3, 3), float32) if not mask else mask + return Array.from_afarray(wrapper.erode(image.arr, mask_.arr)) + + if image.ndim == 3: + mask_ = constant(1, (3, 3, 3), float32) if not mask else mask + return Array.from_afarray(wrapper.erode3(image.arr, mask_.arr)) + + raise ValueError("Image should be either 2D or 3D.") + + +# Wrapping + + +@afarray_as_array +def wrap( + image: Array, + output_size: tuple[int, int], + window: tuple[int, int], + stride: tuple[int, int], + /, + *, + padding: tuple[int, int], + is_column: bool = True, +) -> Array: + return cast( + Array, + wrapper.wrap( + image.arr, + output_size[0], + output_size[1], + window[0], + window[1], + stride[0], + stride[1], + padding[0], + padding[1], + is_column, + ), + ) + + +@afarray_as_array +def unwrap( + image: Array, + output_size: tuple[int, int], + window: tuple[int, int], + stride: tuple[int, int], + /, + *, + padding: tuple[int, int], + is_column: bool = True, +) -> Array: + return cast( + Array, + wrapper.unwrap( + image.arr, + output_size[0], + output_size[1], + window[0], + window[1], + stride[0], + stride[1], + padding[0], + padding[1], + is_column, + ), + ) diff --git a/arrayfire/library/linear_algebra.py b/arrayfire/library/linear_algebra.py index 9ee2de3..3e24c8c 100644 --- a/arrayfire/library/linear_algebra.py +++ b/arrayfire/library/linear_algebra.py @@ -96,7 +96,7 @@ def det(array: Array, /) -> int | float | complex: @afarray_as_array -def inverse(array: Array, /, options: MatProp = MatProp.NONE) -> Array: +def inverse(array: Array, /, *, options: MatProp = MatProp.NONE) -> Array: return cast(Array, wrapper.inverse(array.arr, options)) diff --git a/arrayfire/library/mathematical_functions.py b/arrayfire/library/mathematical_functions.py index c439ae0..f0b802f 100644 --- a/arrayfire/library/mathematical_functions.py +++ b/arrayfire/library/mathematical_functions.py @@ -1,4 +1,72 @@ -from __future__ import annotations +__all__ = [ + "add", + "bitshiftl", + "bitshiftr", + "div", + "mul", + "sub", + "conjg", + "cplx", + "imag", + "real", + "cbrt", + "erf", + "erfc", + "exp", + "expm1", + "factorial", + "lgamma", + "log", + "log1p", + "log2", + "log10", + "pow", + "pow2", + "root", + "rsqrt", + "sqrt", + "tgamma", + "acosh", + "asinh", + "atanh", + "cosh", + "sinh", + "tanh", + "logical_and", + "bitand", + "bitnot", + "bitor", + "bitxor", + "eq", + "ge", + "gt", + "le", + "lt", + "neq", + "logical_not", + "logical_or", + "abs", + "arg", + "ceil", + "clamp", + "floor", + "hypot", + "maxof", + "minof", + "mod", + "neg", + "rem", + "round", + "sign", + "trunc", + "acos", + "asin", + "atan", + "atan2", + "cos", + "sin", + "tan", +] from typing import cast @@ -6,7 +74,6 @@ from arrayfire import Array from arrayfire.array_object import afarray_as_array -from arrayfire.dtypes import is_complex_dtype @afarray_as_array @@ -52,8 +119,6 @@ def mod(x1: int | float | Array, x2: int | float | Array, /) -> Array: If both operands are scalars or if the arrays' shapes do not match. """ - _check_operands_fit_requirements(x1, x2) - x1_ = x1.arr if isinstance(x1, Array) else x1 x2_ = x2.arr if isinstance(x2, Array) else x2 @@ -63,8 +128,6 @@ def mod(x1: int | float | Array, x2: int | float | Array, /) -> Array: @afarray_as_array def pow(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) - x1_ = x1.arr if isinstance(x1, Array) else x1 x2_ = x2.arr if isinstance(x2, Array) else x2 @@ -138,28 +201,22 @@ def clamp(x: Array, /, lo: float, hi: float) -> Array: @afarray_as_array def minof(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) - return cast(Array, wrapper.minof(x1.arr, x2.arr)) @afarray_as_array def maxof(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) - return wrapper.maxof(x1.arr, x2.arr) # type: ignore[arg-type, return-value] @afarray_as_array def rem(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) - return wrapper.rem(x1.arr, x2.arr) # type: ignore[arg-type, return-value] @afarray_as_array def abs(x: Array, /) -> Array: - return wrapper.abs(x.arr) # type: ignore[arg-type, return-value] + return wrapper.abs_(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array @@ -174,7 +231,7 @@ def sign(x: Array, /) -> Array: @afarray_as_array def round(x: Array, /) -> Array: - return wrapper.round(x.arr) # type: ignore[arg-type, return-value] + return wrapper.round_(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array @@ -194,60 +251,50 @@ def ceil(x: Array, /) -> Array: @afarray_as_array def hypot(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) - return wrapper.hypot(x1.arr, x2.arr) # type: ignore[arg-type, return-value] @afarray_as_array def sin(x: Array, /) -> Array: - _check_array_values_not_complex(x) - return wrapper.sin(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def cos(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.cos(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def tan(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.tan(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def asin(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.asin(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def acos(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.acos(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def atan(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.atan(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def atan2(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) return wrapper.atan2(x1.arr, x2.arr) # type: ignore[arg-type, return-value] @afarray_as_array def cplx(x1: int | float | Array, x2: int | float | Array | None, /) -> Array: if x2 is None: - return wrapper.cplx1(x1) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.cplx(x1.arr)) else: - return wrapper.cplx2(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.cplx2(x1.arr, x2.arr)) @afarray_as_array @@ -267,185 +314,149 @@ def conjg(x: Array, /) -> Array: @afarray_as_array def sinh(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.sinh(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def cosh(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.cosh(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def tanh(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.tanh(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def asinh(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.asinh(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def acosh(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.acosh(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def atanh(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.atanh(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def root(x1: int | float | Array, x2: int | float | Array, /) -> Array: - _check_operands_fit_requirements(x1, x2) return wrapper.root(x1.arr, x2.arr) @afarray_as_array def pow2(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.pow2(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def sigmoid(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.sigmoid(x.arr) @afarray_as_array def exp(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.exp(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def expm1(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.expm1(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def erf(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.erf(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def erfc(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.erfc(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def log(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.log(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def log1p(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.log1p(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def log10(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.log10(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def log2(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.log2(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def sqrt(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.sqrt(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def rsqrt(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.rsqrt(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def cbrt(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.cbrt(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def factorial(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.factorial(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def tgamma(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.tgamma(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def lgamma(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.lgamma(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def iszero(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.iszero(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def isinf(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.isinf(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array def isnan(x: Array, /) -> Array: - _check_array_values_not_complex(x) return wrapper.isnan(x.arr) # type: ignore[arg-type, return-value] @afarray_as_array -def and_(x1: Array, x2: Array, /) -> Array: +def logical_and(x1: Array, x2: Array, /) -> Array: return wrapper.and_(x1.arr, x2.arr) # type: ignore[arg-type, return-value] @afarray_as_array -def or_(x1: Array, x2: Array, /) -> Array: +def logical_or(x1: Array, x2: Array, /) -> Array: return wrapper.or_(x1.arr, x2.arr) # type: ignore[arg-type, return-value] @afarray_as_array -def not_(x: Array, /) -> Array: +def logical_not(x: Array, /) -> Array: return wrapper.not_(x.arr) # type: ignore[arg-type, return-value] -def _check_operands_fit_requirements(x1: int | float | Array, x2: int | float | Array) -> None: - if isinstance(x1, Array) and isinstance(x2, Array): - if x1.shape != x2.shape: - raise ValueError("Array shapes must match.") - - if not isinstance(x1, Array) and not isinstance(x2, Array): - raise ValueError("At least one operand must be an Array.") - - -def _check_array_values_not_complex(x: Array) -> None: - if is_complex_dtype(x.dtype): - raise TypeError("Values of an Array should not be the complex numbers.") - pass +@afarray_as_array +def neg(x: Array, /) -> Array: + return cast(Array, wrapper.neg(x.arr)) diff --git a/arrayfire/library/signal_processing.py b/arrayfire/library/signal_processing.py new file mode 100644 index 0000000..17ca430 --- /dev/null +++ b/arrayfire/library/signal_processing.py @@ -0,0 +1,335 @@ +__all__ = [ + "fft", + "fft2", + "fft2_c2r", + "fft2_r2c", + "fft3", + "fft3_c2r", + "fft3_r2c", + "fft_c2r", + "fft_r2c", + "ifft", + "ifft2", + "ifft3", + "set_fft_plan_cache_size", + "fir", + "iir", + "approx1", + "approx1_uniform", + "approx2", + "approx2_uniform", +] + +from typing import cast + +import arrayfire_wrapper.lib as wrapper +from arrayfire_wrapper.lib import set_fft_plan_cache_size + +from arrayfire.array_object import Array, afarray_as_array +from arrayfire.library.constants import ConvDomain, ConvMode, Interp + +# Convolutions + + +@afarray_as_array +def convolve1( + signal: Array, kernel: Array, /, *, mode: ConvMode = ConvMode.DEFAULT, domain: ConvDomain = ConvDomain.AUTO +) -> Array: + return cast(Array, wrapper.convolve1(signal.arr, kernel.arr, mode, domain)) + + +@afarray_as_array +def fft_convolve1(signal: Array, kernel: Array, /, *, mode: ConvMode = ConvMode.DEFAULT) -> Array: + return cast(Array, wrapper.fft_convolve1(signal.arr, kernel.arr, mode)) + + +@afarray_as_array +def convolve2( + signal: Array, kernel: Array, /, *, mode: ConvMode = ConvMode.DEFAULT, domain: ConvDomain = ConvDomain.AUTO +) -> Array: + return cast(Array, wrapper.convolve2(signal.arr, kernel.arr, mode, domain)) + + +@afarray_as_array +def fft_convolve2(signal: Array, kernel: Array, /, *, mode: ConvMode = ConvMode.DEFAULT) -> Array: + return cast(Array, wrapper.fft_convolve2(signal.arr, kernel.arr, mode)) + + +@afarray_as_array +def convolve2_nn( + signal: Array, + kernel: Array, + /, + *, + stride: tuple[int, int] = (1, 1), + padding: tuple[int, int] = (1, 1), + dilation: tuple[int, int] = (1, 1), +) -> Array: + return cast(Array, wrapper.convolve2_nn(signal.arr, kernel.arr, stride, padding, dilation)) + + +@afarray_as_array +def convolve2_separable( + column_kernel: Array, row_kernel: Array, signal: Array, /, *, mode: ConvMode = ConvMode.DEFAULT +) -> Array: + return cast(Array, wrapper.convolve2_sep(column_kernel.arr, row_kernel.arr, signal.arr, mode)) + + +@afarray_as_array +def convolve3( + signal: Array, kernel: Array, /, *, mode: ConvMode = ConvMode.DEFAULT, domain: ConvDomain = ConvDomain.AUTO +) -> Array: + return cast(Array, wrapper.convolve3(signal.arr, kernel.arr, mode, domain)) + + +@afarray_as_array +def fft_convolve3(signal: Array, kernel: Array, /, *, mode: ConvMode = ConvMode.DEFAULT) -> Array: + return cast(Array, wrapper.fft_convolve3(signal.arr, kernel.arr, mode)) + + +# Fast Fourier Transformations + + +def fft(signal: Array, /, *, scale: float | None = None, output_size: int = 0, inplace: bool = False) -> Array: + if inplace: + scale = scale or 1.0 / signal.shape[0] + wrapper.fft_inplace(signal.arr, scale) + return signal + + scale = 1.0 if not scale else scale + return Array.from_afarray(wrapper.fft(signal.arr, scale, output_size)) + + +def fft2( + signal: Array, /, *, scale: float | None = None, output_size: tuple[int, int] = (0, 0), inplace: bool = False +) -> Array: + if inplace: + shape = signal.shape + scale = scale or 1.0 / (shape[0] * shape[1]) + wrapper.fft2_inplace(signal.arr, scale) + return signal + + scale = 1.0 if not scale else scale + return Array.from_afarray(wrapper.fft2(signal.arr, scale, output_size[0], output_size[1])) + + +def fft3( + signal: Array, + /, + *, + scale: float | None = None, + output_size: tuple[int, int, int] = (0, 0, 0), + inplace: bool = False, +) -> Array: + if inplace: + shape = signal.shape + scale = scale or 1.0 / (shape[0] * shape[1] * shape[2]) + wrapper.fft3_inplace(signal.arr, scale) + return signal + + scale = 1.0 if not scale else scale + return Array.from_afarray(wrapper.fft3(signal.arr, scale, output_size[0], output_size[1], output_size[2])) + + +def _convert_to_c2r_dim(axis: int, is_odd: bool, /) -> int: + return 2 * (axis - 1) + int(is_odd) + + +@afarray_as_array +def fft_c2r(signal: Array, /, *, scale: float | None = None, is_odd: bool = False) -> Array: + if not scale: + shape = signal.shape + output_size = _convert_to_c2r_dim(shape[0], is_odd) + scale = 1.0 / output_size + + return cast(Array, wrapper.fft_c2r(signal.arr, scale, is_odd)) + + +@afarray_as_array +def fft2_c2r(signal: Array, /, *, scale: float | None = None, is_odd: bool = False) -> Array: + if not scale: + shape = signal.shape + output_size = _convert_to_c2r_dim(shape[0], is_odd), shape[1] + scale = 1.0 / (output_size[0] * output_size[1]) + + return cast(Array, wrapper.fft2_c2r(signal.arr, scale, is_odd)) + + +@afarray_as_array +def fft3_c2r(signal: Array, /, *, scale: float | None = None, is_odd: bool = False) -> Array: + if not scale: + shape = signal.shape + output_size = _convert_to_c2r_dim(shape[0], is_odd), shape[1], shape[2] + scale = 1.0 / (output_size[0] * output_size[1] * output_size[2]) + + return cast(Array, wrapper.fft3_c2r(signal.arr, scale, is_odd)) + + +@afarray_as_array +def fft_r2c(signal: Array, /, *, scale: float | None = None, output_size: int = 0) -> Array: + scale = scale or 1.0 + return cast(Array, wrapper.fft_r2c(signal.arr, scale, output_size)) + + +@afarray_as_array +def fft2_r2c(signal: Array, /, *, scale: float | None = None, output_size: tuple[int, int] = (0, 0)) -> Array: + scale = scale or 1.0 + return cast(Array, wrapper.fft2_r2c(signal.arr, scale, output_size[0], output_size[1])) + + +@afarray_as_array +def fft3_r2c(signal: Array, /, *, scale: float | None = None, output_size: tuple[int, int, int] = (0, 0, 0)) -> Array: + scale = scale or 1.0 + return cast(Array, wrapper.fft3_r2c(signal.arr, scale, output_size[0], output_size[1], output_size[2])) + + +def ifft( + signal: Array, /, *, scale: float | None = None, output_size: int | None = None, inplace: bool = False +) -> Array: + output_size_ = output_size if output_size and not inplace else signal.shape[:1][0] + scale = scale or 1.0 / output_size_ + + if inplace: + wrapper.ifft_inplace(signal.arr, scale) + return signal + + return Array.from_afarray(wrapper.ifft(signal.arr, scale, output_size_)) + + +def ifft2( + signal: Array, /, *, scale: float | None = None, output_size: tuple[int, int] | None = None, inplace: bool = False +) -> Array: + output_size_ = output_size if output_size and not inplace else signal.shape[:2] + scale = scale or 1.0 / (output_size_[0] * output_size_[1]) + + if inplace: + wrapper.ifft2_inplace(signal.arr, scale) + return signal + + return cast(Array, wrapper.ifft2(signal.arr, scale, output_size_[0], output_size_[1])) + + +def ifft3( + signal: Array, /, *, scale: float | None = None, output_size: tuple[int, int] | None = None, inplace: bool = False +) -> Array: + output_size_ = output_size if output_size and not inplace else signal.shape[:3] + scale = scale or 1.0 / (output_size_[0] * output_size_[1] * output_size_[2]) + + if inplace: + wrapper.ifft3_inplace(signal.arr, scale) + return signal + + return Array.from_afarray(wrapper.ifft3(signal.arr, scale, output_size_[0], output_size_[1], output_size_[2])) + + +# Filter + + +@afarray_as_array +def fir(b: Array, x: Array, /) -> Array: + return cast(Array, wrapper.fir(b.arr, x.arr)) + + +@afarray_as_array +def iir(b: Array, a: Array, x: Array, /) -> Array: + return cast(Array, wrapper.iir(b.arr, a.arr, x.arr)) + + +# Interpolation and approximation + + +def _scale_position_by_axis0(x_current: Array, x_original: Array) -> Array: + x0 = x_original[0, 0, 0, 0] + dx = x_original[1, 0, 0, 0] - x0 + return (x_current - x0) / dx + + +def _scale_position_by_axis1(y_current: Array, y_original: Array) -> Array: + y0 = y_original[0, 0, 0, 0] + dy = y_original[0, 1, 0, 0] - y0 + return (y_current - y0) / dy + + +# TODO +# double check either the approx1_v2 is needed. +@afarray_as_array +def approx1( + signal: Array, + positions: Array, + /, + *, + method: Interp = Interp.LINEAR, + off_grid: float = 0.0, + original_positions: Array | None = None, +) -> Array: + x_position = positions if not original_positions else _scale_position_by_axis0(positions, original_positions) + + return cast(Array, wrapper.approx1(signal.arr, x_position.arr, method, off_grid)) + + +@afarray_as_array +def approx1_uniform( + signal: Array, + positions: Array, + interp_axis: int, + start_index: int, + step_index: int, + /, + *, + method: Interp = Interp.LINEAR, + off_grid: float = 0.0, +) -> Array: + return cast( + Array, + wrapper.approx1_uniform(signal.arr, positions.arr, interp_axis, start_index, step_index, method, off_grid), + ) + + +@afarray_as_array +def approx2( + signal: Array, + positions: tuple[Array, Array], + /, + *, + method: Interp = Interp.LINEAR, + off_grid: float = 0.0, + original_positions: tuple[Array | None, Array | None] = (None, None), +) -> Array: + x_position = ( + positions[0] if not original_positions[0] else _scale_position_by_axis0(positions[0], original_positions[0]) + ) + y_position = ( + positions[1] if not original_positions[1] else _scale_position_by_axis1(positions[1], original_positions[1]) + ) + + return cast(Array, wrapper.approx2(signal.arr, x_position.arr, y_position.arr, method, off_grid)) + + +@afarray_as_array +def approx2_uniform( + signal: Array, + positions: tuple[Array, Array], + interp_axis: tuple[int, int], + start_index: tuple[int, int], + step_index: tuple[int, int], + /, + *, + method: Interp = Interp.LINEAR, + off_grid: float = 0.0, +) -> Array: + return cast( + Array, + wrapper.approx2_uniform( + signal.arr, + positions[0].arr, + interp_axis[0], + start_index[0], + step_index[0], + positions[1].arr, + interp_axis[1], + start_index[1], + step_index[1], + method, + off_grid, + ), + ) diff --git a/arrayfire/library/signal_processing/__init__.py b/arrayfire/library/signal_processing/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/arrayfire/library/signal_processing/fft.py b/arrayfire/library/signal_processing/fft.py deleted file mode 100644 index 3babfa4..0000000 --- a/arrayfire/library/signal_processing/fft.py +++ /dev/null @@ -1 +0,0 @@ -# Note: check for fft_c2r updates if is_odd =True diff --git a/arrayfire/library/vector_algorithms.py b/arrayfire/library/vector_algorithms.py index e910650..a645738 100644 --- a/arrayfire/library/vector_algorithms.py +++ b/arrayfire/library/vector_algorithms.py @@ -273,7 +273,7 @@ def imax(array: Array, /, *, axis: int | None = None) -> tuple[int | float | com def max( - array: Array, /, *, axis: int | None = None, keys: Array | None, ragged_len: Array | None + array: Array, /, *, axis: int | None = None, keys: Array | None = None, ragged_len: Array | None = None ) -> int | float | complex | Array | tuple[Array, Array]: if keys and ragged_len: raise RuntimeError("To process ragged max function, the keys value should be None and vice versa.") From e7e03950746261919b62bad532d31ff37fcd9055 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 3 Feb 2024 22:38:19 +0200 Subject: [PATCH 09/16] Add device and array management --- README.md | 4 +- arrayfire/array_object.py | 70 ++++++- .../library/array_management/__init__.py | 0 .../library/array_management/creation.py | 172 ++++++++++++++++++ arrayfire/library/array_management/helpers.py | 56 ++++++ .../library/array_management/management.py | 22 +++ .../library/array_management/modification.py | 156 ++++++++++++++++ arrayfire/library/constant_array.py | 20 -- arrayfire/library/create_and_modify_array.py | 49 ----- arrayfire/library/data.py | 153 +--------------- arrayfire/library/device.py | 60 +++++- 11 files changed, 529 insertions(+), 233 deletions(-) create mode 100644 arrayfire/library/array_management/__init__.py create mode 100644 arrayfire/library/array_management/creation.py create mode 100644 arrayfire/library/array_management/helpers.py create mode 100644 arrayfire/library/array_management/management.py create mode 100644 arrayfire/library/array_management/modification.py delete mode 100644 arrayfire/library/constant_array.py delete mode 100644 arrayfire/library/create_and_modify_array.py diff --git a/README.md b/README.md index 115afe9..81a3f90 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ Arrayfire python wrapper - [x] Computer Vision - [] Events -- [] Functions to Create and Modify Arrays -- [] Functions to Work with Internal Array Layout +- [x] Functions to Create and Modify Arrays +- [x] Functions to Work with Internal Array Layout - [x] Image Processing - [x] Features - [x] Input and Output Functions diff --git a/arrayfire/array_object.py b/arrayfire/array_object.py index d550ae4..928a517 100755 --- a/arrayfire/array_object.py +++ b/arrayfire/array_object.py @@ -11,7 +11,7 @@ from .dtypes import Dtype from .dtypes import bool as afbool from .dtypes import c_api_value_to_dtype, float32, str_to_dtype -from .library.constant_array import create_constant_array +from .library.array_management.creation import create_constant_array from .library.device import PointerSource if TYPE_CHECKING: @@ -1023,6 +1023,74 @@ def from_afarray(cls, array: wrapper.AFArray) -> Array: out._arr = array return out + @property + def is_linear(self) -> bool: + return wrapper.is_linear(self._arr) + + @property + def is_owner(self) -> bool: + return wrapper.is_owner(self._arr) + + @property + def is_bool(self) -> bool: + return wrapper.is_bool(self._arr) + + @property + def is_column(self) -> bool: + return wrapper.is_column(self._arr) + + @property + def is_row(self) -> bool: + return wrapper.is_row(self._arr) + + @property + def is_complex(self) -> bool: + return wrapper.is_complex(self._arr) + + @property + def is_double(self) -> bool: + return wrapper.is_double(self._arr) + + @property + def is_floating(self) -> bool: + return wrapper.is_floating(self._arr) + + @property + def is_half(self) -> bool: + return wrapper.is_half(self._arr) + + @property + def is_integer(self) -> bool: + return wrapper.is_integer(self._arr) + + @property + def is_real(self) -> bool: + return wrapper.is_real(self._arr) + + @property + def is_real_floating(self) -> bool: + return wrapper.is_realfloating(self._arr) + + @property + def is_single(self) -> bool: + return wrapper.is_single(self._arr) + + @property + def is_sparse(self) -> bool: + return wrapper.is_sparse(self._arr) + + @property + def is_vector(self) -> bool: + return wrapper.is_vector(self._arr) + + @property + def device_pointer(self) -> int: + return wrapper.get_device_ptr(self._arr) + + @property + def is_locked(self) -> bool: + return wrapper.is_locked_array(self._arr) + IndexKey = int | float | complex | bool | wrapper.ParallelRange | slice | tuple[int | slice, ...] | Array diff --git a/arrayfire/library/array_management/__init__.py b/arrayfire/library/array_management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arrayfire/library/array_management/creation.py b/arrayfire/library/array_management/creation.py new file mode 100644 index 0000000..99d49d9 --- /dev/null +++ b/arrayfire/library/array_management/creation.py @@ -0,0 +1,172 @@ +from typing import cast + +import arrayfire_wrapper.lib as wrapper +from arrayfire_wrapper import AFArray + +from arrayfire.array_object import Array, afarray_as_array +from arrayfire.dtypes import Dtype, complex32, float32, implicit_dtype, int64, is_complex_dtype, uint64 +from arrayfire.library.constants import Pad + + +def create_constant_array(number: int | float | complex, shape: tuple[int, ...], dtype: Dtype, /) -> AFArray: + if not dtype: + dtype = implicit_dtype(number, dtype) + + if isinstance(number, complex): + return wrapper.constant_complex(number, shape, dtype if is_complex_dtype(dtype) else complex32) + + if dtype == int64: + return wrapper.constant_long(number, shape, dtype) + + if dtype == uint64: + return wrapper.constant_ulong(number, shape, dtype) + + return wrapper.constant(number, shape, dtype) + + +@afarray_as_array +def diag(array: Array, /, *, diag_index: int = 0, extract: bool = True) -> Array: + if extract: + return cast(Array, wrapper.diag_extract(array.arr, diag_index)) + + return cast(Array, wrapper.diag_create(array.arr, diag_index)) + + +@afarray_as_array +def identity(shape: tuple[int, ...], dtype: Dtype = float32) -> Array: + """ + Create an identity matrix or batch of identity matrices. + + Parameters + ---------- + shape : tuple[int, ...] + The shape of the resulting identity array or batch of arrays. + Must have at least 2 values. + + dtype : Dtype, optional, default: float32 + Data type of the array. + + Returns + ------- + Array + A multi-dimensional ArrayFire array where the first two dimensions + form an identity matrix or batch of matrices. + + Notes + ----- + The `shape` parameter determines the dimensions of the resulting array: + - If shape is (x1, x2), the output is a 2D array of size (x1, x2). + - If shape is (x1, x2, x3), the output is a 3D array of size (x1, x2, x3). + - If shape is (x1, x2, x3, x4), the output is a 4D array of size (x1, x2, x3, x4). + + Raises + ------ + ValueError + If shape is not a tuple or has less than two values. + + Examples + -------- + >>> import arrayfire as af + >>> identity_matrix = af.identity((3, 3)) # Create a 3x3 identity matrix + >>> af.display(identity_matrix) + [3 3 1 1] + 1.0000 0.0000 0.0000 + 0.0000 1.0000 0.0000 + 0.0000 0.0000 1.0000 + + >>> identity_batch = af.identity((2, 2, 3)) # Create a batch of 3 identity 2x2 matrices + >>> af.display(identity_batch) + [2 2 3 1] + 1.0000 0.0000 1.0000 0.0000 1.0000 0.0000 + 0.0000 1.0000 0.0000 1.0000 0.0000 1.0000 + """ + return cast(Array, wrapper.identity(shape, dtype)) + + +@afarray_as_array +def iota(shape: tuple[int, ...], /, *, tile_shape: tuple[int, ...] = (), dtype: Dtype = float32) -> Array: + # if tile_shape: + # min_length = min(len(shape), len(tile_shape)) + # tile_shape = tuple(tile_shape[:min_length] + shape[min_length:]) + # else: + # tile_shape = (1,) * len(shape) + + return cast(Array, wrapper.iota(shape, tile_shape, dtype)) + + +@afarray_as_array +def lower(array: Array, /, *, is_unit_diag: bool = False) -> Array: + return cast(Array, wrapper.lower(array.arr, is_unit_diag)) + + +@afarray_as_array +def upper(array: Array, /, *, is_unit_diag: bool = False) -> Array: + return cast(Array, wrapper.upper(array.arr, is_unit_diag)) + + +@afarray_as_array +def pad( + array: Array, start_shape: tuple[int, ...], end_shape: tuple[int, ...], /, *, fill_type: Pad = Pad.ZERO +) -> Array: + return cast(Array, wrapper.pad(array.arr, start_shape, end_shape, fill_type)) + + +@afarray_as_array +def range(shape: tuple[int, ...], /, *, axis: int = 0, dtype: Dtype = float32) -> Array: + """ + Create a multi-dimensional array using the length of a dimension as a range. + + Parameters + ---------- + shape : tuple[int, ...] + The shape of the resulting array. Each element represents the length + of a corresponding dimension. + + axis : int, optional, default: 0 + The dimension along which the range is calculated. + + dtype : Dtype, optional, default: float32 + Data type of the array. + + Returns + ------- + Array + A multi-dimensional ArrayFire array whose elements along `axis` fall + between [0, self.ndims[axis]-1]. + + Raises + ------ + ValueError + If axis value is greater than the number of axes in resulting Array. + + Notes + ----- + The `shape` parameter determines the dimensions of the resulting array: + - If shape is (x1,), the output is a 1D array of size (x1,). + - If shape is (x1, x2), the output is a 2D array of size (x1, x2). + - If shape is (x1, x2, x3), the output is a 3D array of size (x1, x2, x3). + - If shape is (x1, x2, x3, x4), the output is a 4D array of size (x1, x2, x3, x4). + + Examples + -------- + >>> import arrayfire as af + >>> a = af.range((3, 2)) # axis is not specified, range is along the first dimension. + >>> af.display(a) # The data ranges from [0 - 2] (3 elements along the first dimension) + [3 2 1 1] + 0.0000 0.0000 + 1.0000 1.0000 + 2.0000 2.0000 + + >>> a = af.range((3, 2), axis=1) # axis is 1, range is along the second dimension. + >>> af.display(a) # The data ranges from [0 - 1] (2 elements along the second dimension) + [3 2 1 1] + 0.0000 1.0000 + 0.0000 1.0000 + 0.0000 1.0000 + """ + if axis > len(shape): + raise ValueError( + f"Can not calculate along {axis} dimension. The resulting Array is set to has {len(shape)} dimensions." + ) + + return cast(Array, wrapper.range(shape, axis, dtype)) diff --git a/arrayfire/library/array_management/helpers.py b/arrayfire/library/array_management/helpers.py new file mode 100644 index 0000000..872627d --- /dev/null +++ b/arrayfire/library/array_management/helpers.py @@ -0,0 +1,56 @@ +from typing import cast as typing_cast + +import arrayfire_wrapper.lib as wrapper + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array +from arrayfire.dtypes import Dtype + + +@afarray_as_array +def cast(array: Array, dtype: Dtype, /) -> Array: + """ + Cast an array to a specified type. + + Parameters + ---------- + array : Array + Multi-dimensional arrayfire array to be cast. + dtype : Dtype + The target data type to which the array will be cast. Must be one of the following: + - Dtype.int8 for signed 8-bit integer + - Dtype.int16 for signed 16-bit integer + - Dtype.int32 for signed 32-bit integer + - Dtype.int64 for signed 64-bit integer + - Dtype.uint8 for unsigned 8-bit integer + - Dtype.uint16 for unsigned 16-bit integer + - Dtype.uint32 for unsigned 32-bit integer + - Dtype.uint64 for unsigned 64-bit integer + - Dtype.float16 for 16-bit floating-point + - Dtype.float32 for 32-bit floating-point + - Dtype.float64 for 64-bit floating-point + - Dtype.complex64 for 64-bit complex number + - Dtype.complex128 for 128-bit complex number + - Dtype.bool for boolean + + Returns + ------- + Array + An array containing the values from `array` after conversion to the specified `dtype`. + """ + return typing_cast(Array, wrapper.cast(array.arr, dtype)) + + +@afarray_as_array +def isinf(array: Array, /) -> Array: + return typing_cast(Array, wrapper.isinf(array.arr)) + + +@afarray_as_array +def isnan(array: Array, /) -> Array: + return typing_cast(Array, wrapper.isnan(array.arr)) + + +@afarray_as_array +def iszero(array: Array, /) -> Array: + return typing_cast(Array, wrapper.iszero(array.arr)) diff --git a/arrayfire/library/array_management/management.py b/arrayfire/library/array_management/management.py new file mode 100644 index 0000000..a37790e --- /dev/null +++ b/arrayfire/library/array_management/management.py @@ -0,0 +1,22 @@ +__all__ = ["set_manual_eval_flag", "eval", "copy_array"] + +from typing import cast + +import arrayfire_wrapper.lib as wrapper +from arrayfire_wrapper.lib import set_manual_eval_flag + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array + + +@afarray_as_array +def copy_array(array: Array, /) -> Array: + return cast(Array, wrapper.copy_array(array.arr)) + + +def eval(*arrays: Array) -> None: + if len(arrays) == 1: + wrapper.eval(arrays[0].arr) + + arrs = [array.arr for array in arrays] + wrapper.eval_multiple(len(arrays), *arrs) diff --git a/arrayfire/library/array_management/modification.py b/arrayfire/library/array_management/modification.py new file mode 100644 index 0000000..98e83ad --- /dev/null +++ b/arrayfire/library/array_management/modification.py @@ -0,0 +1,156 @@ +import warnings +from typing import cast + +import arrayfire_wrapper.lib as wrapper + +from arrayfire import Array +from arrayfire.array_object import afarray_as_array + +# Move and reorder + + +@afarray_as_array +def flat(array: Array, /) -> Array: + """ + Flatten the input multi-dimensional array into a 1D array. + + Parameters + ---------- + array : Array + The input multi-dimensional array to be flattened. + + Returns + ------- + Array + A 1D array containing all the elements from the input array. + + Examples + -------- + >>> import arrayfire as af + >>> arr = af.randu(3, 2) # Create a 3x2 random array + >>> flattened = af.flat(arr) # Flatten the array + >>> af.display(flattened) + [6 1 1 1] + 0.8364 + 0.5604 + 0.6352 + 0.0062 + 0.7052 + 0.1676 + """ + return cast(Array, wrapper.flat(array.arr)) + + +@afarray_as_array +def flip(array: Array, /, *, axis: int = 0) -> Array: + return cast(Array, wrapper.flip(array.arr, axis)) + + +@afarray_as_array +def join(axis: int, /, *arrays: Array) -> Array: + if len(arrays) < 2: + raise ValueError("Shape should be at least 2 dimensional.") + if len(arrays) == 2: + return cast(Array, wrapper.join(axis, arrays[0].arr, arrays[1].arr)) + if len(arrays) > 10: + warnings.warn("API is limited to process max of 10 arrays, thus only first 10 units will be processed.") + + afarrays = [array.arr for array in arrays] + return cast(Array, wrapper.join_many(axis, len(arrays), *afarrays)) + + +@afarray_as_array +def moddims(array: Array, shape: tuple[int, ...], /) -> Array: + """ + Modify the shape of the array without changing the data layout. + + Parameters + ---------- + array : af.Array + Multi-dimensional array to be reshaped. + + shape : tuple of int + The desired shape of the output array. It should be a tuple of integers + representing the dimensions of the output array. The product of these + dimensions must match the total number of elements in the input array. + + Returns + ------- + out : af.Array + - An array containing the same data as `array` with the specified shape. + - The total number of elements in `array` must match the product of the + dimensions specified in the `shape` tuple. + + Raises + ------ + ValueError + If the total number of elements in the input array does not match the + product of the dimensions specified in the `shape` tuple. + + Notes + ----- + This function modifies the shape of the input array without changing the + data layout. The resulting array will have the same data, but with a + different shape as specified by the `shape` parameter. + + Examples + -------- + >>> a = af.randu(2, 3, 4) # Create a random 3D array + >>> b = moddims(a, (6, 2)) # Reshape to a 2D array with 6 rows and 2 columns + """ + + return cast(Array, wrapper.moddims(array.arr, shape)) + + +@afarray_as_array +def reorder(array: Array, /, *, shape: tuple[int, ...] = (1, 0, 2, 3)) -> Array: + return cast(Array, wrapper.reorder(array.arr, *shape)) + + +def replace(lhs: Array, rhs: Array | int | float, conditional: Array, /) -> None: + if isinstance(rhs, Array): + wrapper.replace(lhs.arr, conditional.arr, rhs.arr) + return + + wrapper.replace_scalar(lhs.arr, conditional.arr, rhs) + + +def select(lhs: Array | int | float, rhs: Array | int | float, conditional: Array, /) -> None: + if isinstance(lhs, Array) and isinstance(rhs, Array): + wrapper.select(lhs.arr, conditional.arr, rhs.arr) + return + + if isinstance(lhs, Array) and not isinstance(rhs, Array): + wrapper.select_scalar_r(lhs.arr, conditional.arr, rhs) + return + + if not isinstance(lhs, Array) and isinstance(rhs, Array): + wrapper.select_scalar_l(lhs, conditional.arr, rhs.arr) + return + + raise TypeError("At least one array (lhr or rhs) must be of type af.Array.") + + +@afarray_as_array +def shift(array: Array, shape: tuple[int, ...], /) -> Array: + if len(shape) > 4: + raise ValueError("Max 4-dimensional arrays are supported.") + + return cast(Array, wrapper.shift(array.arr, *shape)) + + +@afarray_as_array +def tile(array: Array, shape: tuple[int, ...], /) -> Array: + if len(shape) > 4: + raise ValueError("Max 4-dimensional arrays are supported.") + + return cast(Array, wrapper.tile(array.arr, *shape)) + + +@afarray_as_array +def transpose(array: Array, /, *, conjugate: bool = False, inplace: bool = False) -> Array: + if inplace: + wrapper.transpose_inplace(array.arr, conjugate) + return array + + return cast(Array, wrapper.transpose(array.arr, conjugate)) diff --git a/arrayfire/library/constant_array.py b/arrayfire/library/constant_array.py deleted file mode 100644 index e50d563..0000000 --- a/arrayfire/library/constant_array.py +++ /dev/null @@ -1,20 +0,0 @@ -from arrayfire_wrapper import AFArray -from arrayfire_wrapper.lib import constant, constant_complex, constant_long, constant_ulong - -from arrayfire.dtypes import Dtype, complex32, implicit_dtype, int64, is_complex_dtype, uint64 - - -def create_constant_array(number: int | float | complex, shape: tuple[int, ...], dtype: Dtype, /) -> AFArray: - if not dtype: - dtype = implicit_dtype(number, dtype) - - if isinstance(number, complex): - return constant_complex(number, shape, dtype if is_complex_dtype(dtype) else complex32) - - if dtype == int64: - return constant_long(number, shape, dtype) - - if dtype == uint64: - return constant_ulong(number, shape, dtype) - - return constant(number, shape, dtype) diff --git a/arrayfire/library/create_and_modify_array.py b/arrayfire/library/create_and_modify_array.py deleted file mode 100644 index 6de1168..0000000 --- a/arrayfire/library/create_and_modify_array.py +++ /dev/null @@ -1,49 +0,0 @@ -from typing import cast - -import arrayfire_wrapper.lib as wrapper - -from arrayfire import Array -from arrayfire.array_object import afarray_as_array - - -@afarray_as_array -def moddims(array: Array, shape: tuple[int, ...]) -> Array: - """ - Modify the shape of the array without changing the data layout. - - Parameters - ---------- - array : af.Array - Multi-dimensional array to be reshaped. - - shape : tuple of int - The desired shape of the output array. It should be a tuple of integers - representing the dimensions of the output array. The product of these - dimensions must match the total number of elements in the input array. - - Returns - ------- - out : af.Array - - An array containing the same data as `array` with the specified shape. - - The total number of elements in `array` must match the product of the - dimensions specified in the `shape` tuple. - - Raises - ------ - ValueError - If the total number of elements in the input array does not match the - product of the dimensions specified in the `shape` tuple. - - Notes - ----- - This function modifies the shape of the input array without changing the - data layout. The resulting array will have the same data, but with a - different shape as specified by the `shape` parameter. - - Examples - -------- - >>> a = af.randu(2, 3, 4) # Create a random 3D array - >>> b = moddims(a, (6, 2)) # Reshape to a 2D array with 6 rows and 2 columns - """ - - return cast(Array, wrapper.moddims(array.arr, shape)) diff --git a/arrayfire/library/data.py b/arrayfire/library/data.py index d636e53..e26fb6f 100644 --- a/arrayfire/library/data.py +++ b/arrayfire/library/data.py @@ -5,7 +5,7 @@ from arrayfire import Array from arrayfire.array_object import afarray_as_array from arrayfire.dtypes import Dtype, float32 -from arrayfire.library.constant_array import create_constant_array +from arrayfire.library.array_management.creation import create_constant_array _pyrange = range @@ -45,68 +45,6 @@ def constant(scalar: int | float | complex, shape: tuple[int, ...] = (1,), dtype return cast(Array, result) # HACK actually it return AFArrayType, but decorator makes it an ArrayFire Array. -@afarray_as_array -def range(shape: tuple[int, ...], axis: int = 0, dtype: Dtype = float32) -> Array: - """ - Create a multi-dimensional array using the length of a dimension as a range. - - Parameters - ---------- - shape : tuple[int, ...] - The shape of the resulting array. Each element represents the length - of a corresponding dimension. - - axis : int, optional, default: 0 - The dimension along which the range is calculated. - - dtype : Dtype, optional, default: float32 - Data type of the array. - - Returns - ------- - Array - A multi-dimensional ArrayFire array whose elements along `axis` fall - between [0, self.ndims[axis]-1]. - - Raises - ------ - ValueError - If axis value is greater than the number of axes in resulting Array. - - Notes - ----- - The `shape` parameter determines the dimensions of the resulting array: - - If shape is (x1,), the output is a 1D array of size (x1,). - - If shape is (x1, x2), the output is a 2D array of size (x1, x2). - - If shape is (x1, x2, x3), the output is a 3D array of size (x1, x2, x3). - - If shape is (x1, x2, x3, x4), the output is a 4D array of size (x1, x2, x3, x4). - - Examples - -------- - >>> import arrayfire as af - >>> a = af.range((3, 2)) # axis is not specified, range is along the first dimension. - >>> af.display(a) # The data ranges from [0 - 2] (3 elements along the first dimension) - [3 2 1 1] - 0.0000 0.0000 - 1.0000 1.0000 - 2.0000 2.0000 - - >>> a = af.range((3, 2), axis=1) # axis is 1, range is along the second dimension. - >>> af.display(a) # The data ranges from [0 - 1] (2 elements along the second dimension) - [3 2 1 1] - 0.0000 1.0000 - 0.0000 1.0000 - 0.0000 1.0000 - """ - if axis > len(shape): - raise ValueError( - f"Can not calculate along {axis} dimension. The resulting Array is set to has {len(shape)} dimensions." - ) - - result = wrapper.range(shape, axis, dtype) - return cast(Array, result) # HACK actually it return AFArrayType, but decorator makes it an ArrayFire Array. - - # def iota(d0, d1=None, d2=None, d3=None, dim=-1, tile_dims=None, dtype=Dtype.f32): # """ # Create a multi dimensional array using the number of elements in the array as the range. @@ -173,62 +111,6 @@ def range(shape: tuple[int, ...], axis: int = 0, dtype: Dtype = float32) -> Arra # return out -@afarray_as_array -def identity(shape: tuple[int, ...], dtype: Dtype = float32) -> Array: - """ - Create an identity matrix or batch of identity matrices. - - Parameters - ---------- - shape : tuple[int, ...] - The shape of the resulting identity array or batch of arrays. - Must have at least 2 values. - - dtype : Dtype, optional, default: float32 - Data type of the array. - - Returns - ------- - Array - A multi-dimensional ArrayFire array where the first two dimensions - form an identity matrix or batch of matrices. - - Notes - ----- - The `shape` parameter determines the dimensions of the resulting array: - - If shape is (x1, x2), the output is a 2D array of size (x1, x2). - - If shape is (x1, x2, x3), the output is a 3D array of size (x1, x2, x3). - - If shape is (x1, x2, x3, x4), the output is a 4D array of size (x1, x2, x3, x4). - - Raises - ------ - ValueError - If shape is not a tuple or has less than two values. - - Examples - -------- - >>> import arrayfire as af - >>> identity_matrix = af.identity((3, 3)) # Create a 3x3 identity matrix - >>> af.display(identity_matrix) - [3 3 1 1] - 1.0000 0.0000 0.0000 - 0.0000 1.0000 0.0000 - 0.0000 0.0000 1.0000 - - >>> identity_batch = af.identity((2, 2, 3)) # Create a batch of 3 identity 2x2 matrices - >>> af.display(identity_batch) - [2 2 3 1] - 1.0000 0.0000 1.0000 0.0000 1.0000 0.0000 - 0.0000 1.0000 0.0000 1.0000 0.0000 1.0000 - """ - - if not isinstance(shape, tuple) or len(shape) < 2: - raise ValueError("Argument shape must be a tuple with at least 2 values.") - - result = wrapper.identity(shape, dtype) - return cast(Array, result) - - # def diag(a, num=0, extract=True): # """ # Create a diagonal matrix or Extract the diagonal from a matrix. @@ -581,39 +463,6 @@ def identity(shape: tuple[int, ...], dtype: Dtype = float32) -> Array: # return out -@afarray_as_array -def flat(array: Array) -> Array: - """ - Flatten the input multi-dimensional array into a 1D array. - - Parameters - ---------- - array : Array - The input multi-dimensional array to be flattened. - - Returns - ------- - Array - A 1D array containing all the elements from the input array. - - Examples - -------- - >>> import arrayfire as af - >>> arr = af.randu(3, 2) # Create a 3x2 random array - >>> flattened = af.flat(arr) # Flatten the array - >>> af.display(flattened) - [6 1 1 1] - 0.8364 - 0.5604 - 0.6352 - 0.0062 - 0.7052 - 0.1676 - """ - result = wrapper.flat(array.arr) - return cast(Array, result) - - # def flip(a, dim=0): # """ # Flip an array along a dimension. diff --git a/arrayfire/library/device.py b/arrayfire/library/device.py index ece3a94..c5e2fe1 100644 --- a/arrayfire/library/device.py +++ b/arrayfire/library/device.py @@ -1,6 +1,56 @@ +__all__ = [ + "alloc_device", + "alloc_host", + "alloc_pinned", + "device_gc", + "device_info", + "device_mem_info", + "free_device", + "free_host", + "free_pinned", + "get_dbl_support", + "get_device", + "get_device_count", + "get_half_support", + "get_kernel_cache_directory", + "get_mem_step_size", + "info", + "info_string", + "init", + "print_mem_info", + "set_device", + "sync", + "set_kernel_cache_directory", + "set_mem_step_size", +] + import enum -import arrayfire_wrapper.lib as wrapper +from arrayfire_wrapper.lib import ( + alloc_device, + alloc_host, + device_gc, + device_info, + device_mem_info, + get_kernel_cache_directory, + get_mem_step_size, + print_mem_info, + set_kernel_cache_directory, + set_mem_step_size, + free_host, + free_pinned, + sync, + free_device, + get_device_count, + info, + init, + info_string, + get_dbl_support, + get_half_support, + alloc_pinned, + get_device, + set_device, +) class PointerSource(enum.Enum): @@ -10,11 +60,3 @@ class PointerSource(enum.Enum): device = 0 # gpu host = 1 # cpu - - -def get_device() -> int: # FIXME - return wrapper.get_device() - - -def sync(device_id: int) -> None: # FIXME - return wrapper.sync(device_id) From cb6816ab0e4e1089b4b552788fc94b8548bf1014 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 3 Feb 2024 23:00:19 +0200 Subject: [PATCH 10/16] Remove data. Fix tests --- arrayfire/__init__.py | 16 +- arrayfire/array_object.py | 16 +- .../library/array_management/_constant.py | 20 + .../library/array_management/creation.py | 41 +- arrayfire/library/data.py | 780 ------------------ arrayfire/library/device.py | 24 +- arrayfire/library/image_processing.py | 4 +- tests/array_management/__init__.py | 0 tests/array_management/test_array_creation.py | 71 ++ .../test_array_modification.py | 47 ++ tests/test_data.py | 137 --- .../test_reduction_operations.py | 6 +- 12 files changed, 201 insertions(+), 961 deletions(-) create mode 100644 arrayfire/library/array_management/_constant.py delete mode 100644 arrayfire/library/data.py create mode 100644 tests/array_management/__init__.py create mode 100644 tests/array_management/test_array_creation.py create mode 100644 tests/array_management/test_array_modification.py delete mode 100644 tests/test_data.py diff --git a/arrayfire/__init__.py b/arrayfire/__init__.py index d396714..59cb8c7 100755 --- a/arrayfire/__init__.py +++ b/arrayfire/__init__.py @@ -167,9 +167,9 @@ "root", "rsqrt", "sigmoid", - "and_", - "or_", - "not_", + "logical_and", + "logical_or", + "logical_not", ] @@ -178,7 +178,6 @@ acos, acosh, add, - and_, arg, asin, asinh, @@ -218,14 +217,15 @@ log1p, log2, log10, + logical_and, + logical_not, + logical_or, lt, maxof, minof, mod, mul, neq, - not_, - or_, pow, pow2, real, @@ -245,10 +245,6 @@ trunc, ) -__all__ += ["constant", "range", "identity", "flat"] - -from arrayfire.library.data import constant, flat, identity, range - __all__ += ["randu"] from arrayfire.library.random import randu diff --git a/arrayfire/array_object.py b/arrayfire/array_object.py index 928a517..1c6d5e2 100755 --- a/arrayfire/array_object.py +++ b/arrayfire/array_object.py @@ -11,7 +11,7 @@ from .dtypes import Dtype from .dtypes import bool as afbool from .dtypes import c_api_value_to_dtype, float32, str_to_dtype -from .library.array_management.creation import create_constant_array +from .library.array_management._constant import create_constant_array from .library.device import PointerSource if TYPE_CHECKING: @@ -39,7 +39,7 @@ def afarray_as_array(func: Callable[P, Array]) -> Callable[P, Array]: @wraps(func) def decorated(*args: P.args, **kwargs: P.kwargs) -> Array: result = func(*args, **kwargs) - return Array.from_afarray(result) + return Array.from_afarray(result) # type: ignore[arg-type] # FIXME return decorated @@ -1005,7 +1005,7 @@ def arr(self) -> AFArray: return self._arr @classmethod - def from_afarray(cls, array: wrapper.AFArray) -> Array: + def from_afarray(cls, arr: AFArray) -> Array: """ Creates an instance of Array from an AFArray object. @@ -1020,7 +1020,7 @@ def from_afarray(cls, array: wrapper.AFArray) -> Array: An instance of Array wrapping the given array. """ out = cls() - out._arr = array + out._arr = arr return out @property @@ -1088,9 +1088,15 @@ def device_pointer(self) -> int: return wrapper.get_device_ptr(self._arr) @property - def is_locked(self) -> bool: + def is_locked_array(self) -> bool: return wrapper.is_locked_array(self._arr) + def lock_array(self) -> None: + return wrapper.lock_array(self._arr) + + def unlock_array(self) -> None: + return wrapper.unlock_array(self._arr) + IndexKey = int | float | complex | bool | wrapper.ParallelRange | slice | tuple[int | slice, ...] | Array diff --git a/arrayfire/library/array_management/_constant.py b/arrayfire/library/array_management/_constant.py new file mode 100644 index 0000000..fd87070 --- /dev/null +++ b/arrayfire/library/array_management/_constant.py @@ -0,0 +1,20 @@ +import arrayfire_wrapper.lib as wrapper +from arrayfire_wrapper import AFArray + +from arrayfire.dtypes import Dtype, complex32, implicit_dtype, int64, is_complex_dtype, uint64 + + +def create_constant_array(number: int | float | complex, shape: tuple[int, ...], dtype: Dtype, /) -> AFArray: + if not dtype: + dtype = implicit_dtype(number, dtype) + + if isinstance(number, complex): + return wrapper.constant_complex(number, shape, dtype if is_complex_dtype(dtype) else complex32) + + if dtype == int64: + return wrapper.constant_long(number, shape, dtype) + + if dtype == uint64: + return wrapper.constant_ulong(number, shape, dtype) + + return wrapper.constant(number, shape, dtype) diff --git a/arrayfire/library/array_management/creation.py b/arrayfire/library/array_management/creation.py index 99d49d9..708905b 100644 --- a/arrayfire/library/array_management/creation.py +++ b/arrayfire/library/array_management/creation.py @@ -1,27 +1,44 @@ from typing import cast import arrayfire_wrapper.lib as wrapper -from arrayfire_wrapper import AFArray from arrayfire.array_object import Array, afarray_as_array -from arrayfire.dtypes import Dtype, complex32, float32, implicit_dtype, int64, is_complex_dtype, uint64 +from arrayfire.dtypes import Dtype, float32 from arrayfire.library.constants import Pad +from ._constant import create_constant_array -def create_constant_array(number: int | float | complex, shape: tuple[int, ...], dtype: Dtype, /) -> AFArray: - if not dtype: - dtype = implicit_dtype(number, dtype) - if isinstance(number, complex): - return wrapper.constant_complex(number, shape, dtype if is_complex_dtype(dtype) else complex32) +@afarray_as_array +def constant(scalar: int | float | complex, shape: tuple[int, ...] = (1,), dtype: Dtype = float32) -> Array: + """ + Create a multi-dimensional array filled with a constant value. + + Parameters + ---------- + scalar : int | float | complex + The value to fill each element of the constant array with. - if dtype == int64: - return wrapper.constant_long(number, shape, dtype) + shape : tuple[int, ...], optional, default: (1,) + The shape of the constant array. - if dtype == uint64: - return wrapper.constant_ulong(number, shape, dtype) + dtype : Dtype, optional, default: float32 + Data type of the array. + + Returns + ------- + Array + A multi-dimensional ArrayFire array filled with the specified value. - return wrapper.constant(number, shape, dtype) + Notes + ----- + The shape parameter determines the dimensions of the resulting array: + - If shape is (x1,), the output is a 1D array of size (x1,). + - If shape is (x1, x2), the output is a 2D array of size (x1, x2). + - If shape is (x1, x2, x3), the output is a 3D array of size (x1, x2, x3). + - If shape is (x1, x2, x3, x4), the output is a 4D array of size (x1, x2, x3, x4). + """ + return cast(Array, create_constant_array(scalar, shape, dtype)) @afarray_as_array diff --git a/arrayfire/library/data.py b/arrayfire/library/data.py deleted file mode 100644 index e26fb6f..0000000 --- a/arrayfire/library/data.py +++ /dev/null @@ -1,780 +0,0 @@ -from typing import cast - -import arrayfire_wrapper.lib as wrapper - -from arrayfire import Array -from arrayfire.array_object import afarray_as_array -from arrayfire.dtypes import Dtype, float32 -from arrayfire.library.array_management.creation import create_constant_array - -_pyrange = range - -# TODO add more error handles - - -@afarray_as_array -def constant(scalar: int | float | complex, shape: tuple[int, ...] = (1,), dtype: Dtype = float32) -> Array: - """ - Create a multi-dimensional array filled with a constant value. - - Parameters - ---------- - scalar : int | float | complex - The value to fill each element of the constant array with. - - shape : tuple[int, ...], optional, default: (1,) - The shape of the constant array. - - dtype : Dtype, optional, default: float32 - Data type of the array. - - Returns - ------- - Array - A multi-dimensional ArrayFire array filled with the specified value. - - Notes - ----- - The shape parameter determines the dimensions of the resulting array: - - If shape is (x1,), the output is a 1D array of size (x1,). - - If shape is (x1, x2), the output is a 2D array of size (x1, x2). - - If shape is (x1, x2, x3), the output is a 3D array of size (x1, x2, x3). - - If shape is (x1, x2, x3, x4), the output is a 4D array of size (x1, x2, x3, x4). - """ - result = create_constant_array(scalar, shape, dtype) - return cast(Array, result) # HACK actually it return AFArrayType, but decorator makes it an ArrayFire Array. - - -# def iota(d0, d1=None, d2=None, d3=None, dim=-1, tile_dims=None, dtype=Dtype.f32): -# """ -# Create a multi dimensional array using the number of elements in the array as the range. - -# Parameters -# ---------- -# val : scalar. -# Value of each element of the constant array. - -# d0 : int. -# Length of first dimension. - -# d1 : optional: int. default: None. -# Length of second dimension. - -# d2 : optional: int. default: None. -# Length of third dimension. - -# d3 : optional: int. default: None. -# Length of fourth dimension. - -# tile_dims : optional: tuple of ints. default: None. -# The number of times the data is tiled. - -# dtype : optional: af.Dtype. default: af.Dtype.f32. -# Data type of the array. - -# Returns -# ------- - -# out : af.Array -# Multi dimensional array whose elements are along `dim` fall between [0 - self.elements() - 1]. - -# Examples -# -------- -# >>> import arrayfire as af -# >>> import arrayfire as af -# >>> a = af.iota(3,3) # tile_dim is not specified, data is not tiled -# >>> af.display(a) # the elements range from [0 - 8] (9 elements) -# [3 3 1 1] -# 0.0000 3.0000 6.0000 -# 1.0000 4.0000 7.0000 -# 2.0000 5.0000 8.0000 - -# >>> b = af.iota(3,3,tile_dims(1,2)) # Asking to tile along second dimension. -# >>> af.display(b) -# [3 6 1 1] -# 0.0000 3.0000 6.0000 0.0000 3.0000 6.0000 -# 1.0000 4.0000 7.0000 1.0000 4.0000 7.0000 -# 2.0000 5.0000 8.0000 2.0000 5.0000 8.0000 -# """ -# out = Array() -# dims = dim4(d0, d1, d2, d3) -# td=[1]*4 - -# if tile_dims is not None: -# for i in _brange(len(tile_dims)): -# td[i] = tile_dims[i] - -# tdims = dim4(td[0], td[1], td[2], td[3]) - -# safe_call(backend.get().af_iota(c_pointer(out.arr), 4, c_pointer(dims), -# 4, c_pointer(tdims), dtype.value)) -# return out - - -# def diag(a, num=0, extract=True): -# """ -# Create a diagonal matrix or Extract the diagonal from a matrix. - -# Parameters -# ---------- -# a : af.Array. -# 1 dimensional or 2 dimensional arrayfire array. - -# num : optional: int. default: 0. -# The index of the diagonal. -# - num == 0 signifies the diagonal. -# - num > 0 signifies super diagonals. -# - num < 0 signifies sub diagonals. - -# extract : optional: bool. default: True. -# - If True , diagonal is extracted. `a` has to be 2D. -# - If False, diagonal matrix is created. `a` has to be 1D. - -# Returns -# ------- - -# out : af.Array -# - if extract is True, `out` contains the num'th diagonal from `a`. -# - if extract is False, `out` contains `a` as the num'th diagonal. -# """ -# out = Array() -# if extract: -# safe_call(backend.get().af_diag_extract(c_pointer(out.arr), a.arr, c_int_t(num))) -# else: -# safe_call(backend.get().af_diag_create(c_pointer(out.arr), a.arr, c_int_t(num))) -# return out - - -# def join(dim, first, second, third=None, fourth=None): -# """ -# Join two or more arrayfire arrays along a specified dimension. - -# Parameters -# ---------- - -# dim: int. -# Dimension along which the join occurs. - -# first : af.Array. -# Multi dimensional arrayfire array. - -# second : af.Array. -# Multi dimensional arrayfire array. - -# third : optional: af.Array. default: None. -# Multi dimensional arrayfire array. - -# fourth : optional: af.Array. default: None. -# Multi dimensional arrayfire array. - -# Returns -# ------- - -# out : af.Array -# An array containing the input arrays joined along the specified dimension. - -# Examples -# --------- - -# >>> import arrayfire as af -# >>> a = af.randu(2, 3) -# >>> b = af.randu(2, 3) -# >>> c = af.join(0, a, b) -# >>> d = af.join(1, a, b) -# >>> af.display(a) -# [2 3 1 1] -# 0.9508 0.2591 0.7928 -# 0.5367 0.8359 0.8719 - -# >>> af.display(b) -# [2 3 1 1] -# 0.3266 0.6009 0.2442 -# 0.6275 0.0495 0.6591 - -# >>> af.display(c) -# [4 3 1 1] -# 0.9508 0.2591 0.7928 -# 0.5367 0.8359 0.8719 -# 0.3266 0.6009 0.2442 -# 0.6275 0.0495 0.6591 - -# >>> af.display(d) -# [2 6 1 1] -# 0.9508 0.2591 0.7928 0.3266 0.6009 0.2442 -# 0.5367 0.8359 0.8719 0.6275 0.0495 0.6591 -# """ -# out = Array() -# if third is None and fourth is None: -# safe_call(backend.get().af_join(c_pointer(out.arr), dim, first.arr, second.arr)) -# else: -# c_void_p_4 = c_void_ptr_t * 4 -# c_array_vec = c_void_p_4(first.arr, second.arr, 0, 0) -# num = 2 -# if third is not None: -# c_array_vec[num] = third.arr -# num += 1 -# if fourth is not None: -# c_array_vec[num] = fourth.arr -# num += 1 - -# safe_call(backend.get().af_join_many(c_pointer(out.arr), dim, num, c_pointer(c_array_vec))) -# return out - - -# def tile(a, d0, d1=1, d2=1, d3=1): -# """ -# Tile an array along specified dimensions. - -# Parameters -# ---------- - -# a : af.Array. -# Multi dimensional array. - -# d0: int. -# The number of times `a` has to be tiled along first dimension. - -# d1: optional: int. default: 1. -# The number of times `a` has to be tiled along second dimension. - -# d2: optional: int. default: 1. -# The number of times `a` has to be tiled along third dimension. - -# d3: optional: int. default: 1. -# The number of times `a` has to be tiled along fourth dimension. - -# Returns -# ------- - -# out : af.Array -# An array containing the input after tiling the the specified number of times. - -# Examples -# --------- - -# >>> import arrayfire as af -# >>> a = af.randu(2, 3) -# >>> b = af.tile(a, 2) -# >>> c = af.tile(a, 1, 2) -# >>> d = af.tile(a, 2, 2) -# >>> af.display(a) -# [2 3 1 1] -# 0.9508 0.2591 0.7928 -# 0.5367 0.8359 0.8719 - -# >>> af.display(b) -# [4 3 1 1] -# 0.4107 0.9518 0.4198 -# 0.8224 0.1794 0.0081 -# 0.4107 0.9518 0.4198 -# 0.8224 0.1794 0.0081 - -# >>> af.display(c) -# [2 6 1 1] -# 0.4107 0.9518 0.4198 0.4107 0.9518 0.4198 -# 0.8224 0.1794 0.0081 0.8224 0.1794 0.0081 - -# >>> af.display(d) -# [4 6 1 1] -# 0.4107 0.9518 0.4198 0.4107 0.9518 0.4198 -# 0.8224 0.1794 0.0081 0.8224 0.1794 0.0081 -# 0.4107 0.9518 0.4198 0.4107 0.9518 0.4198 -# 0.8224 0.1794 0.0081 0.8224 0.1794 0.0081 -# """ -# out = Array() -# safe_call(backend.get().af_tile(c_pointer(out.arr), a.arr, d0, d1, d2, d3)) -# return out - - -# def reorder(a, d0=1, d1=0, d2=2, d3=3): -# """ -# Reorder the dimensions of the input. - -# Parameters -# ---------- - -# a : af.Array. -# Multi dimensional array. - -# d0: optional: int. default: 1. -# The location of the first dimension in the output. - -# d1: optional: int. default: 0. -# The location of the second dimension in the output. - -# d2: optional: int. default: 2. -# The location of the third dimension in the output. - -# d3: optional: int. default: 3. -# The location of the fourth dimension in the output. - -# Returns -# ------- - -# out : af.Array -# - An array containing the input aftern reordering its dimensions. - -# Note -# ------ -# - `af.reorder(a, 1, 0)` is the same as `transpose(a)` - -# Examples -# -------- -# >>> import arrayfire as af -# >>> a = af.randu(5, 5, 3) -# >>> af.display(a) -# [5 5 3 1] -# 0.4107 0.0081 0.6600 0.1046 0.8395 -# 0.8224 0.3775 0.0764 0.8827 0.1933 -# 0.9518 0.3027 0.0901 0.1647 0.7270 -# 0.1794 0.6456 0.5933 0.8060 0.0322 -# 0.4198 0.5591 0.1098 0.5938 0.0012 - -# 0.8703 0.9250 0.4387 0.6530 0.4224 -# 0.5259 0.3063 0.3784 0.5476 0.5293 -# 0.1443 0.9313 0.4002 0.8577 0.0212 -# 0.3253 0.8684 0.4390 0.8370 0.1103 -# 0.5081 0.6592 0.4718 0.0618 0.4420 - -# 0.8355 0.6767 0.1033 0.9426 0.9276 -# 0.4878 0.6742 0.2119 0.4817 0.8662 -# 0.2055 0.4523 0.5955 0.9097 0.3578 -# 0.1794 0.1236 0.3745 0.6821 0.6263 -# 0.5606 0.7924 0.9165 0.6056 0.9747 - - -# >>> b = af.reorder(a, 2, 0, 1) -# >>> af.display(b) -# [3 5 5 1] -# 0.4107 0.8224 0.9518 0.1794 0.4198 -# 0.8703 0.5259 0.1443 0.3253 0.5081 -# 0.8355 0.4878 0.2055 0.1794 0.5606 - -# 0.0081 0.3775 0.3027 0.6456 0.5591 -# 0.9250 0.3063 0.9313 0.8684 0.6592 -# 0.6767 0.6742 0.4523 0.1236 0.7924 - -# 0.6600 0.0764 0.0901 0.5933 0.1098 -# 0.4387 0.3784 0.4002 0.4390 0.4718 -# 0.1033 0.2119 0.5955 0.3745 0.9165 - -# 0.1046 0.8827 0.1647 0.8060 0.5938 -# 0.6530 0.5476 0.8577 0.8370 0.0618 -# 0.9426 0.4817 0.9097 0.6821 0.6056 - -# 0.8395 0.1933 0.7270 0.0322 0.0012 -# 0.4224 0.5293 0.0212 0.1103 0.4420 -# 0.9276 0.8662 0.3578 0.6263 0.9747 -# """ -# out = Array() -# safe_call(backend.get().af_reorder(c_pointer(out.arr), a.arr, d0, d1, d2, d3)) -# return out - - -# def shift(a, d0, d1=0, d2=0, d3=0): -# """ -# Shift the input along each dimension. - -# Parameters -# ---------- - -# a : af.Array. -# Multi dimensional array. - -# d0: int. -# The amount of shift along first dimension. - -# d1: optional: int. default: 0. -# The amount of shift along second dimension. - -# d2: optional: int. default: 0. -# The amount of shift along third dimension. - -# d3: optional: int. default: 0. -# The amount of shift along fourth dimension. - -# Returns -# ------- - -# out : af.Array -# - An array the same shape as `a` after shifting it by the specified amounts. - -# Examples -# -------- -# >>> import arrayfire as af -# >>> a = af.randu(3, 3) -# >>> b = af.shift(a, 2) -# >>> c = af.shift(a, 1, -1) -# >>> af.display(a) -# [3 3 1 1] -# 0.7269 0.3569 0.3341 -# 0.7104 0.1437 0.0899 -# 0.5201 0.4563 0.5363 - -# >>> af.display(b) -# [3 3 1 1] -# 0.7104 0.1437 0.0899 -# 0.5201 0.4563 0.5363 -# 0.7269 0.3569 0.3341 - -# >>> af.display(c) -# [3 3 1 1] -# 0.4563 0.5363 0.5201 -# 0.3569 0.3341 0.7269 -# 0.1437 0.0899 0.7104 -# """ -# out = Array() -# safe_call(backend.get().af_shift(c_pointer(out.arr), a.arr, d0, d1, d2, d3)) -# return out - - -# def moddims(a, d0, d1=1, d2=1, d3=1): -# """ -# Modify the shape of the array without changing the data layout. - -# Parameters -# ---------- - -# a : af.Array. -# Multi dimensional array. - -# d0: int. -# The first dimension of output. - -# d1: optional: int. default: 1. -# The second dimension of output. - -# d2: optional: int. default: 1. -# The third dimension of output. - -# d3: optional: int. default: 1. -# The fourth dimension of output. - -# Returns -# ------- - -# out : af.Array -# - An containing the same data as `a` with the specified shape. -# - The number of elements in `a` must match `d0 x d1 x d2 x d3`. -# """ -# out = Array() -# dims = dim4(d0, d1, d2, d3) -# safe_call(backend.get().af_moddims(c_pointer(out.arr), a.arr, 4, c_pointer(dims))) -# return out - - -# def flip(a, dim=0): -# """ -# Flip an array along a dimension. - -# Parameters -# ---------- - -# a : af.Array. -# Multi dimensional array. - -# dim : optional: int. default: 0. -# The dimension along which the flip is performed. - -# Returns -# ------- - -# out : af.Array -# The output after flipping `a` along `dim`. - -# Examples -# --------- - -# >>> import arrayfire as af -# >>> a = af.randu(3, 3) -# >>> af.display(a) -# [3 3 1 1] -# 0.7269 0.3569 0.3341 -# 0.7104 0.1437 0.0899 -# 0.5201 0.4563 0.5363 - -# >>> af.display(b) -# [3 3 1 1] -# 0.5201 0.4563 0.5363 -# 0.7104 0.1437 0.0899 -# 0.7269 0.3569 0.3341 - -# >>> af.display(c) -# [3 3 1 1] -# 0.3341 0.3569 0.7269 -# 0.0899 0.1437 0.7104 -# 0.5363 0.4563 0.5201 - -# """ -# out = Array() -# safe_call(backend.get().af_flip(c_pointer(out.arr), a.arr, c_int_t(dim))) -# return out - - -# def lower(a, is_unit_diag=False): -# """ -# Extract the lower triangular matrix from the input. - -# Parameters -# ---------- - -# a : af.Array. -# Multi dimensional array. - -# is_unit_diag: optional: bool. default: False. -# Flag specifying if the diagonal elements are 1. - -# Returns -# ------- - -# out : af.Array -# An array containing the lower triangular elements from `a`. -# """ -# out = Array() -# safe_call(backend.get().af_lower(c_pointer(out.arr), a.arr, is_unit_diag)) -# return out - - -# def upper(a, is_unit_diag=False): -# """ -# Extract the upper triangular matrix from the input. - -# Parameters -# ---------- - -# a : af.Array. -# Multi dimensional array. - -# is_unit_diag: optional: bool. default: False. -# Flag specifying if the diagonal elements are 1. - -# Returns -# ------- - -# out : af.Array -# An array containing the upper triangular elements from `a`. -# """ -# out = Array() -# safe_call(backend.get().af_upper(c_pointer(out.arr), a.arr, is_unit_diag)) -# return out - - -# def select(cond, lhs, rhs): -# """ -# Select elements from one of two arrays based on condition. - -# Parameters -# ---------- - -# cond : af.Array -# Conditional array - -# lhs : af.Array or scalar -# numerical array whose elements are picked when conditional element is True - -# rhs : af.Array or scalar -# numerical array whose elements are picked when conditional element is False - -# Returns -# -------- - -# out: af.Array -# An array containing elements from `lhs` when `cond` is True and `rhs` when False. - -# Examples -# --------- - -# >>> import arrayfire as af -# >>> a = af.randu(3,3) -# >>> b = af.randu(3,3) -# >>> cond = a > b -# >>> res = af.select(cond, a, b) - -# >>> af.display(a) -# [3 3 1 1] -# 0.4107 0.1794 0.3775 -# 0.8224 0.4198 0.3027 -# 0.9518 0.0081 0.6456 - -# >>> af.display(b) -# [3 3 1 1] -# 0.7269 0.3569 0.3341 -# 0.7104 0.1437 0.0899 -# 0.5201 0.4563 0.5363 - -# >>> af.display(res) -# [3 3 1 1] -# 0.7269 0.3569 0.3775 -# 0.8224 0.4198 0.3027 -# 0.9518 0.4563 0.6456 -# """ -# out = Array() - -# is_left_array = isinstance(lhs, Array) -# is_right_array = isinstance(rhs, Array) - -# if not (is_left_array or is_right_array): -# raise TypeError("Atleast one input needs to be of type arrayfire.array") - -# elif is_left_array and is_right_array: -# safe_call(backend.get().af_select(c_pointer(out.arr), cond.arr, lhs.arr, rhs.arr)) - -# elif _is_number(rhs): -# safe_call(backend.get().af_select_scalar_r(c_pointer(out.arr), cond.arr, lhs.arr, c_double_t(rhs))) -# else: -# safe_call(backend.get().af_select_scalar_l(c_pointer(out.arr), cond.arr, c_double_t(lhs), rhs.arr)) - -# return out - - -# def replace(lhs, cond, rhs): -# """ -# Select elements from one of two arrays based on condition. - -# Parameters -# ---------- - -# lhs : af.Array or scalar -# numerical array whose elements are replaced with `rhs` when conditional element is False - -# cond : af.Array -# Conditional array - -# rhs : af.Array or scalar -# numerical array whose elements are picked when conditional element is False - -# Examples -# --------- -# >>> import arrayfire as af -# >>> a = af.randu(3,3) -# >>> af.display(a) -# [3 3 1 1] -# 0.4107 0.1794 0.3775 -# 0.8224 0.4198 0.3027 -# 0.9518 0.0081 0.6456 - -# >>> cond = (a >= 0.25) & (a <= 0.75) -# >>> af.display(cond) -# [3 3 1 1] -# 1 0 1 -# 0 1 1 -# 0 0 1 - -# >>> af.replace(a, cond, 0.3333) -# >>> af.display(a) -# [3 3 1 1] -# 0.3333 0.1794 0.3333 -# 0.8224 0.3333 0.3333 -# 0.9518 0.0081 0.3333 - -# """ -# is_right_array = isinstance(rhs, Array) - -# if is_right_array: -# safe_call(backend.get().af_replace(lhs.arr, cond.arr, rhs.arr)) -# else: -# safe_call(backend.get().af_replace_scalar(lhs.arr, cond.arr, c_double_t(rhs))) - - -# def pad(a, beginPadding, endPadding, padFillType=PAD.ZERO): -# """ -# Pad an array - -# This function will pad an array with the specified border size. -# Newly padded values can be filled in several different ways. - -# Parameters -# ---------- - -# a: af.Array -# A multi dimensional input arrayfire array. - -# beginPadding: tuple of ints. default: (0, 0, 0, 0). - -# endPadding: tuple of ints. default: (0, 0, 0, 0). - -# padFillType: optional af.PAD default: af.PAD.ZERO -# specifies type of values to fill padded border with - -# Returns -# ------- -# output: af.Array -# A padded array - -# Examples -# --------- -# >>> import arrayfire as af -# >>> a = af.randu(3,3) -# >>> af.display(a) -# [3 3 1 1] -# 0.4107 0.1794 0.3775 -# 0.8224 0.4198 0.3027 -# 0.9518 0.0081 0.6456 - -# >>> padded = af.pad(a, (1, 1), (1, 1), af.ZERO) -# >>> af.display(padded) -# [5 5 1 1] -# 0.0000 0.0000 0.0000 0.0000 0.0000 -# 0.0000 0.4107 0.1794 0.3775 0.0000 -# 0.0000 0.8224 0.4198 0.3027 0.0000 -# 0.0000 0.9518 0.0081 0.6456 0.0000 -# 0.0000 0.0000 0.0000 0.0000 0.0000 -# """ -# out = Array() -# begin_dims = dim4(beginPadding[0], beginPadding[1], beginPadding[2], beginPadding[3]) -# end_dims = dim4(endPadding[0], endPadding[1], endPadding[2], endPadding[3]) - -# safe_call( -# backend.get().af_pad( -# c_pointer(out.arr), a.arr, 4, c_pointer(begin_dims), 4, c_pointer(end_dims), padFillType.value -# ) -# ) -# return out - - -# def lookup(a, idx, dim=0): -# """ -# Lookup the values of input array based on index. - -# Parameters -# ---------- - -# a : af.Array. -# Multi dimensional array. - -# idx : is lookup indices - -# dim : optional: int. default: 0. -# Specifies the dimension for indexing - -# Returns -# ------- - -# out : af.Array -# An array containing values at locations specified by 'idx' - -# Examples -# --------- - -# >>> import arrayfire as af -# >>> arr = af.Array([1,0,3,4,5,6], (2,3)) -# >>> af.display(arr) -# [2 3 1 1] -# 1.0000 3.0000 5.0000 -# 0.0000 4.0000 6.0000 - -# >>> idx = af.array([0, 2]) -# >>> af.lookup(arr, idx, 1) -# [2 2 1 1] -# 1.0000 5.0000 -# 0.0000 6.0000 - -# >>> idx = af.array([0]) -# >>> af.lookup(arr, idx, 0) -# [2 1 1 1] -# 0.0000 -# 2.0000 -# """ -# out = Array() -# safe_call(backend.get().af_lookup(c_pointer(out.arr), a.arr, idx.arr, c_int_t(dim))) -# return out diff --git a/arrayfire/library/device.py b/arrayfire/library/device.py index c5e2fe1..c511011 100644 --- a/arrayfire/library/device.py +++ b/arrayfire/library/device.py @@ -29,27 +29,27 @@ from arrayfire_wrapper.lib import ( alloc_device, alloc_host, + alloc_pinned, device_gc, device_info, device_mem_info, - get_kernel_cache_directory, - get_mem_step_size, - print_mem_info, - set_kernel_cache_directory, - set_mem_step_size, + free_device, free_host, free_pinned, - sync, - free_device, + get_dbl_support, + get_device, get_device_count, + get_half_support, + get_kernel_cache_directory, + get_mem_step_size, info, - init, info_string, - get_dbl_support, - get_half_support, - alloc_pinned, - get_device, + init, + print_mem_info, set_device, + set_kernel_cache_directory, + set_mem_step_size, + sync, ) diff --git a/arrayfire/library/image_processing.py b/arrayfire/library/image_processing.py index 5b9afc1..4f6bee1 100644 --- a/arrayfire/library/image_processing.py +++ b/arrayfire/library/image_processing.py @@ -56,7 +56,7 @@ YCCStd, ) -from .data import constant +from .array_management.creation import constant from .vector_algorithms import max as afmax from .vector_algorithms import min as afmin @@ -96,7 +96,7 @@ def rgb2ycbcr(image: Array, /, *, standard: YCCStd = YCCStd.YCC_601) -> Array: @afarray_as_array -def ycbcr2rgb(image: Array, /, *, standard: YCCStd.YCC_601) -> Array: +def ycbcr2rgb(image: Array, /, *, standard: YCCStd = YCCStd.YCC_601) -> Array: return cast(Array, wrapper.ycbcr2rgb(image.arr, standard)) diff --git a/tests/array_management/__init__.py b/tests/array_management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/array_management/test_array_creation.py b/tests/array_management/test_array_creation.py new file mode 100644 index 0000000..dbcfef6 --- /dev/null +++ b/tests/array_management/test_array_creation.py @@ -0,0 +1,71 @@ +import pytest + +from arrayfire.dtypes import int64 +from arrayfire.library.array_management.creation import constant +from arrayfire.library.array_management.creation import range as afrange + +# Test cases for the constant function + + +def test_constant_1d() -> None: + result = constant(42, (5,)) + assert result.shape == (5,) + assert result.scalar() == 42 + + +def test_constant_2d() -> None: + result = constant(3.14, (3, 4)) + assert result.shape == (3, 4) + assert round(result.scalar(), 2) == 3.14 # type: ignore[arg-type] + + +def test_constant_3d() -> None: + result = constant(0, (2, 2, 2), dtype=int64) + assert result.shape == (2, 2, 2) + assert result.scalar() == 0 + assert result.dtype == int64 + + +def test_constant_default_shape() -> None: + result = constant(1.0) + assert result.shape == (1,) + assert result.scalar() == 1.0 + + +# TODO add error handling +# def test_constant_invalid_dtype() -> None: +# with pytest.raises(ValueError): +# constant(42, (3, 3), dtype="invalid_dtype") + + +# Test cases for the range function + + +def test_range_1d() -> None: + result = afrange((5,)) + assert result.shape == (5,) + + +def test_range_2d() -> None: + result = afrange((3, 4)) + assert result.shape == (3, 4) + + +def test_range_3d() -> None: + result = afrange((2, 2, 2)) + assert result.shape == (2, 2, 2) + + +def test_range_with_axis() -> None: + result = afrange((3, 4), axis=1) + assert result.shape == (3, 4) + + +def test_range_with_dtype() -> None: + result = afrange((4, 3), dtype=int64) + assert result.dtype == int64 + + +def test_range_with_invalid_axis() -> None: + with pytest.raises(ValueError): + afrange((2, 3, 4), axis=4) diff --git a/tests/array_management/test_array_modification.py b/tests/array_management/test_array_modification.py new file mode 100644 index 0000000..8487a98 --- /dev/null +++ b/tests/array_management/test_array_modification.py @@ -0,0 +1,47 @@ +from arrayfire import Array +from arrayfire.library import random +from arrayfire.library.array_management.modification import flat +from arrayfire.library.vector_algorithms import all_true + +# Test cases for the flat function + + +def test_flat_empty_array() -> None: + arr = Array() + flattened = flat(arr) + assert flattened.shape == () + + +def test_flat_1d() -> None: + arr = random.randu((5,)) + flattened = flat(arr) + assert flattened.shape == (5,) + assert all_true(flattened == arr, 0) + + +def test_flat_2d() -> None: + arr = random.randu((3, 2)) + flattened = flat(arr) + assert flattened.shape == (6,) + assert all_true(flattened == flat(arr), 0) + + +def test_flat_3d() -> None: + arr = random.randu((3, 2, 4)) + flattened = flat(arr) + assert flattened.shape == (24,) + assert all_true(flattened == flat(arr), 0) + + +def test_flat_4d() -> None: + arr = random.randu((3, 2, 4, 5)) + flattened = flat(arr) + assert flattened.shape == (120,) + assert all_true(flattened == flat(arr), 0) + + +def test_flat_large_array() -> None: + arr = random.randu((1000, 1000)) + flattened = flat(arr) + assert flattened.shape == (1000000,) + assert all_true(flattened == flat(arr), 0) diff --git a/tests/test_data.py b/tests/test_data.py deleted file mode 100644 index 86eac95..0000000 --- a/tests/test_data.py +++ /dev/null @@ -1,137 +0,0 @@ -import pytest - -from arrayfire import Array -from arrayfire.dtypes import int64 -from arrayfire.library import data, random -from arrayfire.library.vector_algorithms import all_true - -# Test cases for the constant function - - -def test_constant_1d() -> None: - result = data.constant(42, (5,)) - assert result.shape == (5,) - assert result.scalar() == 42 - - -def test_constant_2d() -> None: - result = data.constant(3.14, (3, 4)) - assert result.shape == (3, 4) - assert round(result.scalar(), 2) == 3.14 # type: ignore[arg-type] - - -def test_constant_3d() -> None: - result = data.constant(0, (2, 2, 2), dtype=int64) - assert result.shape == (2, 2, 2) - assert result.scalar() == 0 - assert result.dtype == int64 - - -def test_constant_default_shape() -> None: - result = data.constant(1.0) - assert result.shape == (1,) - assert result.scalar() == 1.0 - - -# TODO add error handling -# def test_constant_invalid_dtype() -> None: -# with pytest.raises(ValueError): -# data.constant(42, (3, 3), dtype="invalid_dtype") - - -# Test cases for the range function - - -def test_range_1d() -> None: - result = data.range((5,)) - assert result.shape == (5,) - - -def test_range_2d() -> None: - result = data.range((3, 4)) - assert result.shape == (3, 4) - - -def test_range_3d() -> None: - result = data.range((2, 2, 2)) - assert result.shape == (2, 2, 2) - - -def test_range_with_axis() -> None: - result = data.range((3, 4), axis=1) - assert result.shape == (3, 4) - - -def test_range_with_dtype() -> None: - result = data.range((4, 3), dtype=int64) - assert result.dtype == int64 - - -def test_range_with_invalid_axis() -> None: - with pytest.raises(ValueError): - data.range((2, 3, 4), axis=4) - - -# Test cases for the identity function - -# BUG in to_list() -# def test_identity_2x2() -> None: -# result = data.identity((2, 2)) -# expected = [[1, 0], [0, 1]] -# assert result.to_list() == expected - - -# def test_identity_3x3() -> None: -# result = data.identity((3, 3)) -# expected = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] -# assert result.to_list() == expected - - -# def test_identity_2x2x2() -> None: -# result = data.identity((2, 2, 2)) -# expected = [[[1, 0], [0, 1]], [[1, 0], [0, 1]]] -# assert result.to_list() == expected - - -# Test cases for the flat function - - -def test_flat_empty_array() -> None: - arr = Array() - flattened = data.flat(arr) - assert flattened.shape == () - - -def test_flat_1d() -> None: - arr = random.randu((5,)) - flattened = data.flat(arr) - assert flattened.shape == (5,) - assert all_true(flattened == arr, 0) - - -def test_flat_2d() -> None: - arr = random.randu((3, 2)) - flattened = data.flat(arr) - assert flattened.shape == (6,) - assert all_true(flattened == data.flat(arr), 0) - - -def test_flat_3d() -> None: - arr = random.randu((3, 2, 4)) - flattened = data.flat(arr) - assert flattened.shape == (24,) - assert all_true(flattened == data.flat(arr), 0) - - -def test_flat_4d() -> None: - arr = random.randu((3, 2, 4, 5)) - flattened = data.flat(arr) - assert flattened.shape == (120,) - assert all_true(flattened == data.flat(arr), 0) - - -def test_flat_large_array() -> None: - arr = random.randu((1000, 1000)) - flattened = data.flat(arr) - assert flattened.shape == (1000000,) - assert all_true(flattened == data.flat(arr), 0) diff --git a/tests/vector_algorithms/test_reduction_operations.py b/tests/vector_algorithms/test_reduction_operations.py index f16b831..6e3379f 100644 --- a/tests/vector_algorithms/test_reduction_operations.py +++ b/tests/vector_algorithms/test_reduction_operations.py @@ -1,18 +1,18 @@ import pytest from arrayfire import Array -from arrayfire.library import data from arrayfire.library import vector_algorithms as va +from arrayfire.library.array_management.creation import constant @pytest.fixture def true_array() -> Array: - return data.constant(1, (5, 5)) + return constant(1, (5, 5)) @pytest.fixture def false_array() -> Array: - arr = data.constant(1, (5, 5)) + arr = constant(1, (5, 5)) arr[2, 2] = 0 # Set one element to False return arr From 1ea29481d3c4654671a28b5e46323ce8aa968e33 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 3 Feb 2024 23:30:37 +0200 Subject: [PATCH 11/16] Fix imports --- arrayfire/__init__.py | 24 ++ arrayfire/array_object.py | 2 + arrayfire/dtypes.py | 120 ++----- arrayfire/library/_backend_functions.py | 305 ------------------ .../library/array_management/_constant.py | 8 +- arrayfire/library/utils.py | 82 ----- arrayfire/logger.py | 18 -- arrayfire/version.py | 9 +- requirements.txt | 2 +- 9 files changed, 68 insertions(+), 502 deletions(-) delete mode 100644 arrayfire/library/_backend_functions.py delete mode 100644 arrayfire/library/utils.py delete mode 100755 arrayfire/logger.py diff --git a/arrayfire/__init__.py b/arrayfire/__init__.py index 59cb8c7..181f7ba 100755 --- a/arrayfire/__init__.py +++ b/arrayfire/__init__.py @@ -11,6 +11,7 @@ from .array_object import Array __all__ += [ + "Dtype", "b8", "bool", "c32", @@ -40,6 +41,7 @@ ] from .dtypes import ( + Dtype, b8, bool, c32, @@ -249,6 +251,28 @@ from arrayfire.library.random import randu +__all__ += ["corrcoef", "cov", "mean", "median", "stdev", "topk", "var"] + +from arrayfire.library.statistics import corrcoef, cov, mean, median, stdev, topk, var + +__all__ += [ + "get_active_backend", + "get_available_backends", + "get_backend_count", + "get_backend_id", + "get_device_id", + "set_backend", +] + +from arrayfire.library.unified_api_functions import ( + get_active_backend, + get_available_backends, + get_backend_count, + get_backend_id, + get_device_id, + set_backend, +) + __all__ += [ "accum", "scan", diff --git a/arrayfire/array_object.py b/arrayfire/array_object.py index 1c6d5e2..1b010e2 100755 --- a/arrayfire/array_object.py +++ b/arrayfire/array_object.py @@ -1,5 +1,7 @@ from __future__ import annotations +__all__ = ["Array"] + import array as _pyarray from collections.abc import Callable from functools import wraps diff --git a/arrayfire/dtypes.py b/arrayfire/dtypes.py index ac10fee..c817aff 100644 --- a/arrayfire/dtypes.py +++ b/arrayfire/dtypes.py @@ -1,8 +1,37 @@ -from __future__ import annotations +__all__ = [ + "Dtype", + "b8", + "bool", + "c32", + "c64", + "complex32", + "complex64", + "f16", + "f32", + "f64", + "float16", + "float32", + "float64", + "int16", + "int32", + "int64", + "s16", + "s32", + "s64", + "u8", + "u16", + "u32", + "u64", + "uint8", + "uint16", + "uint32", + "uint64", +] + from typing import TypeAlias -_pybool: TypeAlias = bool +py_bool: TypeAlias = bool from arrayfire_wrapper import ( # noqa Dtype, @@ -63,92 +92,9 @@ b8, ) -# CType = Type[ctypes._SimpleCData] - - -# @dataclass(frozen=True) -# class Dtype: -# name: str -# typecode: str -# c_type: CType -# typename: str -# c_api_value: int # Internal use only - -# def __str__(self) -> str: -# return self.name - -# def __repr__(self) -> str: -# return f"arrayfire.{self.name}(typecode<{self.typecode}>)" - - -# Specification required -# int8 = Dtype("int8", "i8", ctypes.c_char, "int8", 4) # HACK int8 - Not Supported, b8? -# int16 = Dtype("int16", "h", ctypes.c_short, "short int", 10) -# int32 = Dtype("int32", "i", ctypes.c_int, "int", 5) -# int64 = Dtype("int64", "l", ctypes.c_longlong, "long int", 8) -# uint8 = Dtype("uint8", "B", ctypes.c_ubyte, "unsigned_char", 7) -# uint16 = Dtype("uint16", "H", ctypes.c_ushort, "unsigned short int", 11) -# uint32 = Dtype("uint32", "I", ctypes.c_uint, "unsigned int", 6) -# uint64 = Dtype("uint64", "L", ctypes.c_ulonglong, "unsigned long int", 9) -# float16 = Dtype("float16", "e", ctypes.c_uint16, "half", 12) -# float32 = Dtype("float32", "f", ctypes.c_float, "float", 0) -# float64 = Dtype("float64", "d", ctypes.c_double, "double", 2) -# complex64 = Dtype("complex64", "F", ctypes.c_float * 2, "float complex", 1) # type: ignore[arg-type] -# complex128 = Dtype("complex128", "D", ctypes.c_double * 2, "double complex", 3) # type: ignore[arg-type] -# bool = Dtype("bool", "b", ctypes.c_bool, "bool", 4) - -# supported_dtypes = ( -# int16, -# int32, -# int64, -# uint8, -# uint16, -# uint32, -# uint64, -# float16, -# float32, -# float64, -# complex64, -# complex128, -# bool, -# int8, # BUG if place on top of the list -# ) - - -def is_complex_dtype(dtype: Dtype) -> _pybool: - return dtype in {complex32, complex64} - - -# c_dim_t = ctypes.c_int if is_arch_x86() else ctypes.c_longlong -# ShapeType = tuple[int, ...] - - -# class CShape(tuple): -# def __new__(cls, *args: int) -> CShape: -# cls.original_shape = len(args) -# return tuple.__new__(cls, args) - -# def __init__(self, x1: int = 1, x2: int = 1, x3: int = 1, x4: int = 1) -> None: -# self.x1 = x1 -# self.x2 = x2 -# self.x3 = x3 -# self.x4 = x4 - -# def __repr__(self) -> str: -# return f"{self.__class__.__name__}{self.x1, self.x2, self.x3, self.x4}" - -# @property -# def c_array(self): # type: ignore[no-untyped-def] -# c_shape = c_dim_t * 4 # ctypes.c_int | ctypes.c_longlong * 4 -# return c_shape(c_dim_t(self.x1), c_dim_t(self.x2), c_dim_t(self.x3), c_dim_t(self.x4)) - - -# def to_str(c_str: ctypes.c_char_p) -> str: -# return str(c_str.value.decode("utf-8")) # type: ignore[union-attr] - -def implicit_dtype(number: int | float | _pybool | complex, array_dtype: Dtype) -> Dtype: - if isinstance(number, _pybool): +def implicit_dtype(number: int | float | py_bool | complex, array_dtype: Dtype) -> Dtype: + if isinstance(number, py_bool): number_dtype = bool elif isinstance(number, int): number_dtype = int64 diff --git a/arrayfire/library/_backend_functions.py b/arrayfire/library/_backend_functions.py deleted file mode 100644 index 5d708a5..0000000 --- a/arrayfire/library/_backend_functions.py +++ /dev/null @@ -1,305 +0,0 @@ -# FIXME -# from __future__ import annotations - -# import warnings -# from enum import Enum -# from typing import TYPE_CHECKING - -# from arrayfire_wrapper import Backend, BackendType, get_backend -# from arrayfire_wrapper.lib import cublas_set_math_mode -# from arrayfire_wrapper.lib import get_backend_count as c_get_backend_count -# from arrayfire_wrapper.lib import get_backend_id as c_get_backend_id -# from arrayfire_wrapper.lib import get_device_id as c_get_device_id -# from arrayfire_wrapper.lib import get_native_id as c_get_native_id -# from arrayfire_wrapper.lib import get_size_of as c_get_size_of -# from arrayfire_wrapper.lib import get_stream as c_get_stream -# from arrayfire_wrapper.lib import set_backend as c_set_backend -# from arrayfire_wrapper.lib import set_native_id as c_set_native_id - -# if TYPE_CHECKING: -# from arrayfire import Array -# from arrayfire.dtypes import Dtype - - -# class CublasMathMode(Enum): -# default = 0 -# tensor_op = 1 - - -# def set_backend(backend_type: BackendType | str) -> None: -# """ -# Set a specific backend by backend_type name. - -# Parameters -# ---------- -# backend_type : BackendType | str -# Name of the backend type to set. - -# Raises -# ------ -# ValueError -# If the given backend_type name is not a valid name for backend backend_type. -# TypeError -# If the given backend_type is not a valid type for backend backend_type. -# RuntimeError -# If the given backend_type is already the active backend backend_type. -# RuntimeError -# If the given backend_type could not be set as new backend backend_type. -# """ -# backend = get_backend() -# current_active_backend_type = backend.backend_type - -# if isinstance(backend_type, str): -# if backend_type not in [d.name for d in BackendType]: -# raise ValueError(f"{backend_type} is not a valid name for backend backend_type.") -# backend_type = BackendType[backend_type] - -# if not isinstance(backend_type, BackendType): -# raise TypeError(f"{backend_type} is not a valid type for backend backend_type.") - -# if current_active_backend_type == backend_type: -# raise RuntimeError(f"{backend_type} is already the active backend backend_type.") - -# if backend.backend_type == BackendType.unified: -# c_set_backend(backend_type.value) - -# # NOTE keep in mind that this operation works in-place -# # FIXME should not access private API -# backend._load_backend_lib(backend_type) - -# if current_active_backend_type == backend.backend_type: -# raise RuntimeError(f"Could not set {backend_type} as new backend backend_type. Consider checking logs.") - - -# def get_array_backend_name(array: Array) -> str: -# """ -# Get the name of the backend on which the Array is located. - -# Parameters -# ---------- -# array : Array -# The Array to get the backend name of. - -# Returns -# ------- -# value : str -# Name of the backend on which the Array is located. -# """ - -# id_ = c_get_backend_id(array.arr) -# return BackendType(id_).name - - -# def get_backend_id(array: Array) -> str: -# warnings.warn("Was renamed. Now get_array_backend_name() in main repo.", DeprecationWarning) -# return get_array_backend_name(array) - - -# def get_backend_count() -> int: -# """ -# Get a number of available backends. - -# Returns -# ------- - -# value : int -# Number of available backends. -# """ - -# return c_get_backend_count() - - -# def get_active_backend() -> Backend: -# """ -# Get the current active backend. - -# value : Backend -# Current active backend. -# """ - -# # TODO do not deprecate -# warnings.warn("A user has access explicitly only to the active backend.", DeprecationWarning) -# return get_backend() - - -# def get_available_backends() -> Backend: -# """ -# Get the list of available backends. - -# Returns -# ------- -# value : Backend -# Current active backend. -# """ - -# # TODO do not deprecate -# warnings.warn( -# "A user has access explicitly only to the active backend. Thus returning only active backend.", -# DeprecationWarning, -# ) -# return get_active_backend() - - -# def get_array_device_id(array: Array) -> int: -# """ -# Get the id of the device on which the Array was created. - -# Parameters -# ---------- -# array : Array -# The Array to get the device id of. - -# Returns -# ------- -# value : int -# The id of the device on which the Array was created. -# """ - -# return c_get_device_id(array.arr) - - -# def get_device_id(array: Array) -> int: -# warnings.warn("Was renamed due to unintuitive function name. Now get_array_device_id().", DeprecationWarning) -# return get_array_device_id(array) - - -# def get_dtype_size(dtype: Dtype) -> int: -# """ -# Get the size of the type represented by Dtype. - -# Parameters -# ---------- -# dtype : Dtype -# The type to get the size of. - -# Returns -# ------- -# value : int -# The size of the type in bytes. -# """ - -# return c_get_size_of(dtype) - - -# def get_size_of(dtype: Dtype) -> int: -# warnings.warn("Was renamed due to unintuitive function name. Now get_dtype_size().", DeprecationWarning) -# return get_dtype_size(dtype) - - -# # Previously module arrayfire.cuda - - -# def _check_if_cuda_used() -> None: -# backend = get_backend() -# if backend.backend_type != BackendType.cuda: -# raise RuntimeError( -# f"Can not get the CUDA stream id because the other backend is in use: {backend.backend_type}." -# ) - - -# def get_stream(index: int) -> int: -# warnings.warn("Was renamed due to unintuitive function name. Now get_cuda_stream().", DeprecationWarning) -# return get_cuda_stream(index) - - -# def get_cuda_stream(index: int) -> int: -# """ -# Get the CUDA stream used for the device id by ArrayFire. - -# Parameters -# ---------- -# idx : int -# Specifies the index of the device. - -# Returns -# ------- -# value : int -# Denoting the stream id. - -# Raises -# ------ -# RuntimeError -# If the current backend type is not CUDA. -# """ -# _check_if_cuda_used() - -# return c_get_stream(index) - - -# def get_native_id(index: int) -> int: -# warnings.warn("Was renamed due to unintuitive function name. Now get_native_cuda_id().", DeprecationWarning) -# return get_native_cuda_id(index) - - -# def get_native_cuda_id(index: int) -> int: -# """ -# Get native (unsorted) CUDA device id. - -# Parameters -# ---------- -# idx : int -# Specifies the (sorted) index of the device. - -# Returns -# ------- -# value : int -# Denoting the native cuda id. - -# Raises -# ------ -# RuntimeError -# If the current backend type is not CUDA. -# """ -# _check_if_cuda_used() - -# return c_get_native_id(index) - - -# def set_native_id(index: int) -> None: -# warnings.warn("Was renamed due to unintuitive function name. Now get_native_cuda_id().", DeprecationWarning) -# return set_native_cuda_id(index) - - -# def set_native_cuda_id(index: int) -> None: -# """ -# Set native (unsorted) CUDA device id. - -# Parameters -# ---------- -# idx : int -# Specifies the (unsorted) native index of the device. - -# Raises -# ------ -# RuntimeError -# If the current backend type is not CUDA. -# """ -# _check_if_cuda_used() - -# return c_set_native_id(index) - - -# def set_cublas_mode(mode: CublasMathMode | int = CublasMathMode.default) -> None: -# """ -# Set cuBLAS math mode for CUDA backend. It enables the Tensor Core usage if available on CUDA backend GPUs. - -# Parameters -# ---------- -# mode : CublasMathMode | int -# Specify the mode available within CublasMathMode enum. - -# Raises -# ------ -# ValueError -# If the given math mode int value is not a valid value for cuBLAS math mode. -# RuntimeError -# If the current backend type is not CUDA. -# """ -# if isinstance(mode, int): -# if mode not in [m.value for m in CublasMathMode]: -# raise ValueError(f"{mode} is not supported as cublas math mode.") -# mode = CublasMathMode(mode) - -# _check_if_cuda_used() - -# return cublas_set_math_mode(mode.value) diff --git a/arrayfire/library/array_management/_constant.py b/arrayfire/library/array_management/_constant.py index fd87070..c5b82f0 100644 --- a/arrayfire/library/array_management/_constant.py +++ b/arrayfire/library/array_management/_constant.py @@ -1,7 +1,11 @@ import arrayfire_wrapper.lib as wrapper from arrayfire_wrapper import AFArray -from arrayfire.dtypes import Dtype, complex32, implicit_dtype, int64, is_complex_dtype, uint64 +from arrayfire.dtypes import Dtype, complex32, complex64, implicit_dtype, int64, uint64 + + +def _is_complex_dtype(dtype: Dtype) -> bool: + return dtype in {complex32, complex64} def create_constant_array(number: int | float | complex, shape: tuple[int, ...], dtype: Dtype, /) -> AFArray: @@ -9,7 +13,7 @@ def create_constant_array(number: int | float | complex, shape: tuple[int, ...], dtype = implicit_dtype(number, dtype) if isinstance(number, complex): - return wrapper.constant_complex(number, shape, dtype if is_complex_dtype(dtype) else complex32) + return wrapper.constant_complex(number, shape, dtype if _is_complex_dtype(dtype) else complex32) if dtype == int64: return wrapper.constant_long(number, shape, dtype) diff --git a/arrayfire/library/utils.py b/arrayfire/library/utils.py deleted file mode 100644 index 413c7d6..0000000 --- a/arrayfire/library/utils.py +++ /dev/null @@ -1,82 +0,0 @@ -from typing import cast as typing_cast - -import arrayfire_wrapper.lib as wrapper - -from arrayfire.array_object import Array, afarray_as_array -from arrayfire.dtypes import Dtype - - -@afarray_as_array -def cast(array: Array, dtype: Dtype, /) -> Array: - """ - Cast an array to a specified type. - - Parameters - ---------- - array : Array - Multi-dimensional arrayfire array to be cast. - dtype : Dtype - The target data type to which the array will be cast. Must be one of the following: - - Dtype.int8 for signed 8-bit integer - - Dtype.int16 for signed 16-bit integer - - Dtype.int32 for signed 32-bit integer - - Dtype.int64 for signed 64-bit integer - - Dtype.uint8 for unsigned 8-bit integer - - Dtype.uint16 for unsigned 16-bit integer - - Dtype.uint32 for unsigned 32-bit integer - - Dtype.uint64 for unsigned 64-bit integer - - Dtype.float16 for 16-bit floating-point - - Dtype.float32 for 32-bit floating-point - - Dtype.float64 for 64-bit floating-point - - Dtype.complex64 for 64-bit complex number - - Dtype.complex128 for 128-bit complex number - - Dtype.bool for boolean - - Returns - ------- - Array - An array containing the values from `array` after conversion to the specified `dtype`. - """ - return typing_cast(Array, wrapper.cast(array.arr, dtype)) - - -# def timeit(af_func, *args): -# """ -# Function to time arrayfire functions. - -# Parameters -# ---------- - -# af_func : arrayfire function - -# *args : arguments to `af_func` - -# Returns -# -------- - -# t : Time in seconds -# """ - -# sample_trials = 3 - -# sample_time = 1E20 - -# for i in range(sample_trials): -# start = time() -# res = af_func(*args) -# eval(res) -# sync() -# sample_time = min(sample_time, time() - start) - -# if (sample_time >= 0.5): -# return sample_time - -# num_iters = max(math.ceil(1.0 / sample_time), 3.0) - -# start = time() -# for i in range(int(num_iters)): -# res = af_func(*args) -# eval(res) -# sync() -# sample_time = (time() - start) / num_iters -# return sample_time diff --git a/arrayfire/logger.py b/arrayfire/logger.py deleted file mode 100755 index 25c48d7..0000000 --- a/arrayfire/logger.py +++ /dev/null @@ -1,18 +0,0 @@ -import logging - -# Configure the logger -logging.basicConfig(level=logging.INFO) - -# Create a logger -logger = logging.getLogger(__name__) - -# Create a console handler and set the level to DEBUG -console_handler = logging.StreamHandler() -console_handler.setLevel(logging.INFO) - -# Create a formatter and attach it to the console handler -formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") -console_handler.setFormatter(formatter) - -# Add the console handler to the logger -logger.addHandler(console_handler) diff --git a/arrayfire/version.py b/arrayfire/version.py index 54d94c7..ae862ac 100644 --- a/arrayfire/version.py +++ b/arrayfire/version.py @@ -1,7 +1,7 @@ import os -_MAJOR = "4" -_MINOR = "0" +_MAJOR = "0" +_MINOR = "1" # On main and in a nightly release the patch should be one ahead of the last # released build. _PATCH = "0" @@ -11,8 +11,3 @@ VERSION_SHORT = "{0}.{1}".format(_MAJOR, _MINOR) VERSION = "{0}.{1}.{2}{3}".format(_MAJOR, _MINOR, _PATCH, _SUFFIX) - -FORGE_VER_MAJOR = "1" -ARRAYFIRE_VER_MAJOR = "3" -ARRAYFIRE_VER_MINOR = "8" -ARRAYFIRE_VERSION = "{0}.{1}".format(ARRAYFIRE_VER_MAJOR, ARRAYFIRE_VER_MINOR) diff --git a/requirements.txt b/requirements.txt index 87151f9..1146e2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -git+https://github.com/roaffix/arrayfire-python-wrapper # TODO fix versioning dependency +arrayfire-python-wrapper~=0.4.0+af3.9.0 From 62fb8825cd07333df2ea2284b6de8c2708969dff Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 3 Feb 2024 23:51:26 +0200 Subject: [PATCH 12/16] Fix imports --- arrayfire/__init__.py | 322 ++++++++++++++++++++++--- arrayfire/library/constants.py | 7 + arrayfire/library/image_processing.py | 2 +- arrayfire/library/input_and_output.py | 13 +- arrayfire/library/signal_processing.py | 3 + requirements.txt | 2 +- 6 files changed, 318 insertions(+), 31 deletions(-) diff --git a/arrayfire/__init__.py b/arrayfire/__init__.py index 181f7ba..03ca3e9 100755 --- a/arrayfire/__init__.py +++ b/arrayfire/__init__.py @@ -1,11 +1,11 @@ # flake8: noqa -from .version import ARRAYFIRE_VERSION, VERSION +from .version import VERSION __all__ = ["__version__"] __version__ = VERSION -__all__ += ["__arrayfire_version__"] -__arrayfire_version__ = ARRAYFIRE_VERSION +# TODO +# add __arrayfire_version__ __all__ += ["Array"] from .array_object import Array @@ -70,31 +70,247 @@ uint64, ) -# __all__ += [ -# "get_active_backend", # DeprecationWarning -# "get_array_backend_name", -# "get_array_device_id", -# "get_available_backends", # DeprecationWarning -# "get_backend_count", -# "get_backend_id", # DeprecationWarning -# "get_device_id", # DeprecationWarning -# "get_dtype_size", -# "get_size_of", # DeprecationWarning -# "set_backend", -# ] - -# from .library._backend_functions import ( -# get_active_backend, -# get_array_backend_name, -# get_array_device_id, -# get_available_backends, -# get_backend_count, -# get_backend_id, -# get_device_id, -# get_dtype_size, -# get_size_of, -# set_backend, -# ) +__all__ += [ + "Match", + "MatProp", + "BinaryOperator", + "Norm", + "ConvGradient", + "VarianceBias", + "TopK", + "ImageFormat", + "CSpace", + "YCCStd", + "Flux", + "Diffusion", + "CannyThreshold", + "Connectivity", + "ConvDomain", + "ConvMode", + "Interp", + "IterativeDeconv", + "Pad", + "pi", +] + +from arrayfire.library.constants import ( + BinaryOperator, + CannyThreshold, + Connectivity, + ConvDomain, + ConvGradient, + ConvMode, + CSpace, + Diffusion, + Flux, + ImageFormat, + Interp, + IterativeDeconv, + Match, + MatProp, + Norm, + Pad, + TopK, + VarianceBias, + YCCStd, + pi, +) + +__all__ = [ + "alloc_device", + "alloc_host", + "alloc_pinned", + "device_gc", + "device_info", + "device_mem_info", + "free_device", + "free_host", + "free_pinned", + "get_dbl_support", + "get_device", + "get_device_count", + "get_half_support", + "get_kernel_cache_directory", + "get_mem_step_size", + "info", + "info_string", + "init", + "print_mem_info", + "set_device", + "sync", + "set_kernel_cache_directory", + "set_mem_step_size", +] + +from arrayfire.library.device import ( + alloc_device, + alloc_host, + alloc_pinned, + device_gc, + device_info, + device_mem_info, + free_device, + free_host, + free_pinned, + get_dbl_support, + get_device, + get_device_count, + get_half_support, + get_kernel_cache_directory, + get_mem_step_size, + info, + info_string, + init, + print_mem_info, + set_device, + set_kernel_cache_directory, + set_mem_step_size, + sync, +) + +__all__ += [ + "color_space", + "gray2rgb", + "hsv2rgb", + "rgb2gray", + "rgb2hsv", + "rgb2ycbcr", + "anisotropic_diffusion", + "bilateral", + "canny", + "inverse_deconv", + "iterative_deconv", + "maxfilt", + "mean_shift", + "medfilt", + "medfilt1", + "medfilt2", + "minfilt", + "sat", + "sobel_operator", + "gaussian_kernel", + "hist_equal", + "histogram", + "resize", + "rotate", + "scale", + "skew", + "transform", + "transform_coordinates", + "translate", + "confidence_cc", + "regions", + "dilate", + "erode", + "wrap", + "unwrap", +] + +from arrayfire.library.image_processing import ( + anisotropic_diffusion, + bilateral, + canny, + color_space, + confidence_cc, + dilate, + erode, + gaussian_kernel, + gray2rgb, + hist_equal, + histogram, + hsv2rgb, + inverse_deconv, + iterative_deconv, + maxfilt, + mean_shift, + medfilt, + medfilt1, + medfilt2, + minfilt, + regions, + resize, + rgb2gray, + rgb2hsv, + rgb2ycbcr, + rotate, + sat, + scale, + skew, + sobel_operator, + transform, + transform_coordinates, + translate, + unwrap, + wrap, +) + +__all__ += [ + "is_image_io_available", + "read_array", + "save_array", + "load_image", + "load_image_native", + "load_image_memory", + "delete_image_memory", + "save_image", + "save_image_native", + "save_image_memory", +] + +from arrayfire.library.input_and_output import ( + delete_image_memory, + is_image_io_available, + load_image, + load_image_memory, + load_image_native, + read_array, + save_array, + save_image, + save_image_memory, + save_image_native, +) + +__all__ += ["cublas_set_math_mode", "get_native_id", "get_stream", "set_native_id"] + +from arrayfire.library.interface_functions import cublas_set_math_mode, get_native_id, get_stream, set_native_id + +__all__ += [ + "dot", + "gemm", + "matmul", + "is_lapack_available", + "cholesky", + "lu", + "qr", + "svd", + "det", + "inverse", + "norm", + "pinverse", + "rank", + "solve", +] + +from arrayfire.library.linear_algebra import ( + cholesky, + det, + dot, + gemm, + inverse, + is_lapack_available, + lu, + matmul, + norm, + pinverse, + qr, + rank, + solve, + svd, +) + +__all__ += ["convolve2_gradient_nn"] + +from arrayfire.library.machine_learning import convolve2_gradient_nn __all__ += [ "add", @@ -251,6 +467,56 @@ from arrayfire.library.random import randu +__all__ += [ + "fft", + "fft2", + "fft2_c2r", + "fft2_r2c", + "fft3", + "fft3_c2r", + "fft3_r2c", + "fft_c2r", + "fft_r2c", + "fft_convolve1", + "fft_convolve2", + "fft_convolve3", + "ifft", + "ifft2", + "ifft3", + "set_fft_plan_cache_size", + "fir", + "iir", + "approx1", + "approx1_uniform", + "approx2", + "approx2_uniform", +] + +from arrayfire.library.signal_processing import ( + approx1, + approx1_uniform, + approx2, + approx2_uniform, + fft, + fft2, + fft2_c2r, + fft2_r2c, + fft3, + fft3_c2r, + fft3_r2c, + fft_c2r, + fft_convolve1, + fft_convolve2, + fft_convolve3, + fft_r2c, + fir, + ifft, + ifft2, + ifft3, + iir, + set_fft_plan_cache_size, +) + __all__ += ["corrcoef", "cov", "mean", "median", "stdev", "topk", "var"] from arrayfire.library.statistics import corrcoef, cov, mean, median, stdev, topk, var diff --git a/arrayfire/library/constants.py b/arrayfire/library/constants.py index 23361c0..f6cfb43 100644 --- a/arrayfire/library/constants.py +++ b/arrayfire/library/constants.py @@ -37,6 +37,13 @@ "YCCStd", "Flux", "Diffusion", + "CannyThreshold", + "Connectivity", + "ConvDomain", + "ConvMode", + "Interp", + "IterativeDeconv", + "Pad", ] import math diff --git a/arrayfire/library/image_processing.py b/arrayfire/library/image_processing.py index 4f6bee1..c74b9ed 100644 --- a/arrayfire/library/image_processing.py +++ b/arrayfire/library/image_processing.py @@ -40,10 +40,10 @@ from typing import cast import arrayfire_wrapper.lib as wrapper -from dtypes import Dtype, float32 from arrayfire import Array from arrayfire.array_object import afarray_as_array +from arrayfire.dtypes import Dtype, float32 from arrayfire.library.constants import ( CannyThreshold, Connectivity, diff --git a/arrayfire/library/input_and_output.py b/arrayfire/library/input_and_output.py index 7e22e99..bf89b3c 100644 --- a/arrayfire/library/input_and_output.py +++ b/arrayfire/library/input_and_output.py @@ -1,4 +1,15 @@ -__all__ = ["is_image_io_available", "read_array", "save_array"] +__all__ = [ + "is_image_io_available", + "read_array", + "save_array", + "load_image", + "load_image_native", + "load_image_memory", + "delete_image_memory", + "save_image", + "save_image_native", + "save_image_memory", +] from pathlib import Path from typing import cast diff --git a/arrayfire/library/signal_processing.py b/arrayfire/library/signal_processing.py index 17ca430..93ef03b 100644 --- a/arrayfire/library/signal_processing.py +++ b/arrayfire/library/signal_processing.py @@ -8,6 +8,9 @@ "fft3_r2c", "fft_c2r", "fft_r2c", + "fft_convolve1", + "fft_convolve2", + "fft_convolve3", "ifft", "ifft2", "ifft3", diff --git a/requirements.txt b/requirements.txt index 1146e2c..06ce0a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -arrayfire-python-wrapper~=0.4.0+af3.9.0 +arrayfire-python-wrapper~=0.4.0 From 5492463099a52d2cee8428747867ea5cd75617c3 Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 4 Feb 2024 05:05:33 +0200 Subject: [PATCH 13/16] Fix tests. Move constant creation. FIx imports --- arrayfire/__init__.py | 74 +++++- arrayfire/array_object.py | 10 +- .../creation.py => array_functions.py} | 223 +++++++++++++++++- .../library/array_management/_constant.py | 24 -- .../library/array_management/management.py | 22 -- .../library/array_management/modification.py | 156 ------------ arrayfire/library/computer_vision.py | 2 + arrayfire/library/image_processing.py | 2 +- .../{array_management/helpers.py => utils.py} | 15 -- tests/array_management/test_array_creation.py | 71 ------ .../test_array_modification.py | 47 ---- tests/array_object/__init__.py | 0 .../test_array_object}/__init__.py | 0 .../test_initialization.py | 0 .../test_methods.py | 0 .../test_operator_overrides.py | 0 .../__init__.py | 0 tests/test_library/test_array_functions.py | 113 +++++++++ tests/{ => test_library}/test_operators.py | 15 +- tests/{ => test_library}/test_random.py | 0 .../test_vector_algorithms.py} | 40 ++++ tests/vector_algorithms/__init__.py | 0 .../test_reduction_operations.py | 39 --- 23 files changed, 455 insertions(+), 398 deletions(-) rename arrayfire/library/{array_management/creation.py => array_functions.py} (50%) delete mode 100644 arrayfire/library/array_management/_constant.py delete mode 100644 arrayfire/library/array_management/management.py delete mode 100644 arrayfire/library/array_management/modification.py rename arrayfire/library/{array_management/helpers.py => utils.py} (80%) delete mode 100644 tests/array_management/test_array_creation.py delete mode 100644 tests/array_management/test_array_modification.py delete mode 100644 tests/array_object/__init__.py rename {arrayfire/library/array_management => tests/test_array_object}/__init__.py (100%) rename tests/{array_object => test_array_object}/test_initialization.py (100%) mode change 100755 => 100644 rename tests/{array_object => test_array_object}/test_methods.py (100%) rename tests/{array_object => test_array_object}/test_operator_overrides.py (100%) rename tests/{array_management => test_library}/__init__.py (100%) create mode 100644 tests/test_library/test_array_functions.py rename tests/{ => test_library}/test_operators.py (62%) mode change 100755 => 100644 rename tests/{ => test_library}/test_random.py (100%) rename tests/{vector_algorithms/test_inclusive_scan_operations.py => test_library/test_vector_algorithms.py} (59%) delete mode 100644 tests/vector_algorithms/__init__.py delete mode 100644 tests/vector_algorithms/test_reduction_operations.py diff --git a/arrayfire/__init__.py b/arrayfire/__init__.py index 03ca3e9..d342569 100755 --- a/arrayfire/__init__.py +++ b/arrayfire/__init__.py @@ -70,6 +70,74 @@ uint64, ) +__all__ += [ + "constant", + "diag", + "identity", + "iota", + "lower", + "upper", + "pad", + "range", + "isinf", + "isnan", + "iszero", + "set_manual_eval_flag", + "eval", + "copy_array", + "flat", + "flip", + "join", + "moddims", + "reorder", + "replace", + "select", + "shift", + "tile", + "transpose", +] + +from arrayfire.library.array_functions import ( + constant, + copy_array, + diag, + eval, + flat, + flip, + identity, + iota, + isinf, + isnan, + iszero, + join, + lower, + moddims, + pad, + range, + reorder, + replace, + select, + set_manual_eval_flag, + shift, + tile, + transpose, + upper, +) + +__all__ += ["gloh", "orb", "sift", "dog", "fast", "harris", "susan", "hamming_matcher", "nearest_neighbour"] + +from arrayfire.library.computer_vision import ( + dog, + fast, + gloh, + hamming_matcher, + harris, + nearest_neighbour, + orb, + sift, + susan, +) + __all__ += [ "Match", "MatProp", @@ -116,7 +184,7 @@ pi, ) -__all__ = [ +__all__ += [ "alloc_device", "alloc_host", "alloc_pinned", @@ -582,3 +650,7 @@ sum, where, ) + +__all__ += ["cast"] + +from arrayfire.library.utils import cast diff --git a/arrayfire/array_object.py b/arrayfire/array_object.py index 1b010e2..542dd22 100755 --- a/arrayfire/array_object.py +++ b/arrayfire/array_object.py @@ -13,7 +13,6 @@ from .dtypes import Dtype from .dtypes import bool as afbool from .dtypes import c_api_value_to_dtype, float32, str_to_dtype -from .library.array_management._constant import create_constant_array from .library.device import PointerSource if TYPE_CHECKING: @@ -794,10 +793,9 @@ def __setitem__(self, key: IndexKey, value: int | float | bool | Array, /) -> No dims = _get_processed_index(key, self.shape) if is_array_with_bool: ndims = 1 - # FIXME - other_arr = create_constant_array(value, (int(num),), self.dtype) # type: ignore[call-overload] + other_arr = wrapper.create_constant_array(value, (int(num.real),), self.dtype) else: - other_arr = create_constant_array(value, dims, self.dtype) + other_arr = wrapper.create_constant_array(value, dims, self.dtype) del_other = True else: other_arr = value.arr @@ -1125,10 +1123,10 @@ def _process_c_function(lhs: int | float | Array, rhs: int | float | Array, c_fu elif isinstance(lhs, Array) and isinstance(rhs, (int, float)): lhs_array = lhs.arr - rhs_array = create_constant_array(rhs, lhs.shape, lhs.dtype) + rhs_array = wrapper.create_constant_array(rhs, lhs.shape, lhs.dtype) elif isinstance(lhs, (int, float)) and isinstance(rhs, Array): - lhs_array = create_constant_array(lhs, rhs.shape, rhs.dtype) + lhs_array = wrapper.create_constant_array(lhs, rhs.shape, rhs.dtype) rhs_array = rhs.arr else: diff --git a/arrayfire/library/array_management/creation.py b/arrayfire/library/array_functions.py similarity index 50% rename from arrayfire/library/array_management/creation.py rename to arrayfire/library/array_functions.py index 708905b..c654598 100644 --- a/arrayfire/library/array_management/creation.py +++ b/arrayfire/library/array_functions.py @@ -1,12 +1,41 @@ +__all__ = [ + "constant", + "diag", + "identity", + "iota", + "lower", + "upper", + "pad", + "range", + "isinf", + "isnan", + "iszero", + "set_manual_eval_flag", + "eval", + "copy_array", + "flat", + "flip", + "join", + "moddims", + "reorder", + "replace", + "select", + "shift", + "tile", + "transpose", +] + +import warnings from typing import cast import arrayfire_wrapper.lib as wrapper +from arrayfire_wrapper.lib import set_manual_eval_flag from arrayfire.array_object import Array, afarray_as_array from arrayfire.dtypes import Dtype, float32 from arrayfire.library.constants import Pad -from ._constant import create_constant_array +# Array creation @afarray_as_array @@ -38,7 +67,7 @@ def constant(scalar: int | float | complex, shape: tuple[int, ...] = (1,), dtype - If shape is (x1, x2, x3), the output is a 3D array of size (x1, x2, x3). - If shape is (x1, x2, x3, x4), the output is a 4D array of size (x1, x2, x3, x4). """ - return cast(Array, create_constant_array(scalar, shape, dtype)) + return cast(Array, wrapper.create_constant_array(scalar, shape, dtype)) @afarray_as_array @@ -102,12 +131,6 @@ def identity(shape: tuple[int, ...], dtype: Dtype = float32) -> Array: @afarray_as_array def iota(shape: tuple[int, ...], /, *, tile_shape: tuple[int, ...] = (), dtype: Dtype = float32) -> Array: - # if tile_shape: - # min_length = min(len(shape), len(tile_shape)) - # tile_shape = tuple(tile_shape[:min_length] + shape[min_length:]) - # else: - # tile_shape = (1,) * len(shape) - return cast(Array, wrapper.iota(shape, tile_shape, dtype)) @@ -187,3 +210,187 @@ def range(shape: tuple[int, ...], /, *, axis: int = 0, dtype: Dtype = float32) - ) return cast(Array, wrapper.range(shape, axis, dtype)) + + +# Helper functions + + +@afarray_as_array +def isinf(array: Array, /) -> Array: + return cast(Array, wrapper.isinf(array.arr)) + + +@afarray_as_array +def isnan(array: Array, /) -> Array: + return cast(Array, wrapper.isnan(array.arr)) + + +@afarray_as_array +def iszero(array: Array, /) -> Array: + return cast(Array, wrapper.iszero(array.arr)) + + +# Functions to manage array + + +@afarray_as_array +def copy_array(array: Array, /) -> Array: + return cast(Array, wrapper.copy_array(array.arr)) + + +def eval(*arrays: Array) -> None: + if len(arrays) == 1: + wrapper.eval(arrays[0].arr) + + arrs = [array.arr for array in arrays] + wrapper.eval_multiple(len(arrays), *arrs) + + +# Move and reorder + + +@afarray_as_array +def flat(array: Array, /) -> Array: + """ + Flatten the input multi-dimensional array into a 1D array. + + Parameters + ---------- + array : Array + The input multi-dimensional array to be flattened. + + Returns + ------- + Array + A 1D array containing all the elements from the input array. + + Examples + -------- + >>> import arrayfire as af + >>> arr = af.randu(3, 2) # Create a 3x2 random array + >>> flattened = af.flat(arr) # Flatten the array + >>> af.display(flattened) + [6 1 1 1] + 0.8364 + 0.5604 + 0.6352 + 0.0062 + 0.7052 + 0.1676 + """ + return cast(Array, wrapper.flat(array.arr)) + + +@afarray_as_array +def flip(array: Array, /, *, axis: int = 0) -> Array: + return cast(Array, wrapper.flip(array.arr, axis)) + + +@afarray_as_array +def join(axis: int, /, *arrays: Array) -> Array: + if len(arrays) < 2: + raise ValueError("Shape should be at least 2 dimensional.") + if len(arrays) == 2: + return cast(Array, wrapper.join(axis, arrays[0].arr, arrays[1].arr)) + if len(arrays) > 10: + warnings.warn("API is limited to process max of 10 arrays, thus only first 10 units will be processed.") + + afarrays = [array.arr for array in arrays] + return cast(Array, wrapper.join_many(axis, len(arrays), *afarrays)) + + +@afarray_as_array +def moddims(array: Array, shape: tuple[int, ...], /) -> Array: + """ + Modify the shape of the array without changing the data layout. + + Parameters + ---------- + array : af.Array + Multi-dimensional array to be reshaped. + + shape : tuple of int + The desired shape of the output array. It should be a tuple of integers + representing the dimensions of the output array. The product of these + dimensions must match the total number of elements in the input array. + + Returns + ------- + out : af.Array + - An array containing the same data as `array` with the specified shape. + - The total number of elements in `array` must match the product of the + dimensions specified in the `shape` tuple. + + Raises + ------ + ValueError + If the total number of elements in the input array does not match the + product of the dimensions specified in the `shape` tuple. + + Notes + ----- + This function modifies the shape of the input array without changing the + data layout. The resulting array will have the same data, but with a + different shape as specified by the `shape` parameter. + + Examples + -------- + >>> a = af.randu(2, 3, 4) # Create a random 3D array + >>> b = moddims(a, (6, 2)) # Reshape to a 2D array with 6 rows and 2 columns + """ + + return cast(Array, wrapper.moddims(array.arr, shape)) + + +@afarray_as_array +def reorder(array: Array, /, *, shape: tuple[int, ...] = (1, 0, 2, 3)) -> Array: + return cast(Array, wrapper.reorder(array.arr, *shape)) + + +def replace(lhs: Array, rhs: Array | int | float, conditional: Array, /) -> None: + if isinstance(rhs, Array): + wrapper.replace(lhs.arr, conditional.arr, rhs.arr) + return + + wrapper.replace_scalar(lhs.arr, conditional.arr, rhs) + + +def select(lhs: Array | int | float, rhs: Array | int | float, conditional: Array, /) -> None: + if isinstance(lhs, Array) and isinstance(rhs, Array): + wrapper.select(lhs.arr, conditional.arr, rhs.arr) + return + + if isinstance(lhs, Array) and not isinstance(rhs, Array): + wrapper.select_scalar_r(lhs.arr, conditional.arr, rhs) + return + + if not isinstance(lhs, Array) and isinstance(rhs, Array): + wrapper.select_scalar_l(lhs, conditional.arr, rhs.arr) + return + + raise TypeError("At least one array (lhr or rhs) must be of type af.Array.") + + +@afarray_as_array +def shift(array: Array, shape: tuple[int, ...], /) -> Array: + if len(shape) > 4: + raise ValueError("Max 4-dimensional arrays are supported.") + + return cast(Array, wrapper.shift(array.arr, *shape)) + + +@afarray_as_array +def tile(array: Array, shape: tuple[int, ...], /) -> Array: + if len(shape) > 4: + raise ValueError("Max 4-dimensional arrays are supported.") + + return cast(Array, wrapper.tile(array.arr, *shape)) + + +@afarray_as_array +def transpose(array: Array, /, *, conjugate: bool = False, inplace: bool = False) -> Array: + if inplace: + wrapper.transpose_inplace(array.arr, conjugate) + return array + + return cast(Array, wrapper.transpose(array.arr, conjugate)) diff --git a/arrayfire/library/array_management/_constant.py b/arrayfire/library/array_management/_constant.py deleted file mode 100644 index c5b82f0..0000000 --- a/arrayfire/library/array_management/_constant.py +++ /dev/null @@ -1,24 +0,0 @@ -import arrayfire_wrapper.lib as wrapper -from arrayfire_wrapper import AFArray - -from arrayfire.dtypes import Dtype, complex32, complex64, implicit_dtype, int64, uint64 - - -def _is_complex_dtype(dtype: Dtype) -> bool: - return dtype in {complex32, complex64} - - -def create_constant_array(number: int | float | complex, shape: tuple[int, ...], dtype: Dtype, /) -> AFArray: - if not dtype: - dtype = implicit_dtype(number, dtype) - - if isinstance(number, complex): - return wrapper.constant_complex(number, shape, dtype if _is_complex_dtype(dtype) else complex32) - - if dtype == int64: - return wrapper.constant_long(number, shape, dtype) - - if dtype == uint64: - return wrapper.constant_ulong(number, shape, dtype) - - return wrapper.constant(number, shape, dtype) diff --git a/arrayfire/library/array_management/management.py b/arrayfire/library/array_management/management.py deleted file mode 100644 index a37790e..0000000 --- a/arrayfire/library/array_management/management.py +++ /dev/null @@ -1,22 +0,0 @@ -__all__ = ["set_manual_eval_flag", "eval", "copy_array"] - -from typing import cast - -import arrayfire_wrapper.lib as wrapper -from arrayfire_wrapper.lib import set_manual_eval_flag - -from arrayfire import Array -from arrayfire.array_object import afarray_as_array - - -@afarray_as_array -def copy_array(array: Array, /) -> Array: - return cast(Array, wrapper.copy_array(array.arr)) - - -def eval(*arrays: Array) -> None: - if len(arrays) == 1: - wrapper.eval(arrays[0].arr) - - arrs = [array.arr for array in arrays] - wrapper.eval_multiple(len(arrays), *arrs) diff --git a/arrayfire/library/array_management/modification.py b/arrayfire/library/array_management/modification.py deleted file mode 100644 index 98e83ad..0000000 --- a/arrayfire/library/array_management/modification.py +++ /dev/null @@ -1,156 +0,0 @@ -import warnings -from typing import cast - -import arrayfire_wrapper.lib as wrapper - -from arrayfire import Array -from arrayfire.array_object import afarray_as_array - -# Move and reorder - - -@afarray_as_array -def flat(array: Array, /) -> Array: - """ - Flatten the input multi-dimensional array into a 1D array. - - Parameters - ---------- - array : Array - The input multi-dimensional array to be flattened. - - Returns - ------- - Array - A 1D array containing all the elements from the input array. - - Examples - -------- - >>> import arrayfire as af - >>> arr = af.randu(3, 2) # Create a 3x2 random array - >>> flattened = af.flat(arr) # Flatten the array - >>> af.display(flattened) - [6 1 1 1] - 0.8364 - 0.5604 - 0.6352 - 0.0062 - 0.7052 - 0.1676 - """ - return cast(Array, wrapper.flat(array.arr)) - - -@afarray_as_array -def flip(array: Array, /, *, axis: int = 0) -> Array: - return cast(Array, wrapper.flip(array.arr, axis)) - - -@afarray_as_array -def join(axis: int, /, *arrays: Array) -> Array: - if len(arrays) < 2: - raise ValueError("Shape should be at least 2 dimensional.") - if len(arrays) == 2: - return cast(Array, wrapper.join(axis, arrays[0].arr, arrays[1].arr)) - if len(arrays) > 10: - warnings.warn("API is limited to process max of 10 arrays, thus only first 10 units will be processed.") - - afarrays = [array.arr for array in arrays] - return cast(Array, wrapper.join_many(axis, len(arrays), *afarrays)) - - -@afarray_as_array -def moddims(array: Array, shape: tuple[int, ...], /) -> Array: - """ - Modify the shape of the array without changing the data layout. - - Parameters - ---------- - array : af.Array - Multi-dimensional array to be reshaped. - - shape : tuple of int - The desired shape of the output array. It should be a tuple of integers - representing the dimensions of the output array. The product of these - dimensions must match the total number of elements in the input array. - - Returns - ------- - out : af.Array - - An array containing the same data as `array` with the specified shape. - - The total number of elements in `array` must match the product of the - dimensions specified in the `shape` tuple. - - Raises - ------ - ValueError - If the total number of elements in the input array does not match the - product of the dimensions specified in the `shape` tuple. - - Notes - ----- - This function modifies the shape of the input array without changing the - data layout. The resulting array will have the same data, but with a - different shape as specified by the `shape` parameter. - - Examples - -------- - >>> a = af.randu(2, 3, 4) # Create a random 3D array - >>> b = moddims(a, (6, 2)) # Reshape to a 2D array with 6 rows and 2 columns - """ - - return cast(Array, wrapper.moddims(array.arr, shape)) - - -@afarray_as_array -def reorder(array: Array, /, *, shape: tuple[int, ...] = (1, 0, 2, 3)) -> Array: - return cast(Array, wrapper.reorder(array.arr, *shape)) - - -def replace(lhs: Array, rhs: Array | int | float, conditional: Array, /) -> None: - if isinstance(rhs, Array): - wrapper.replace(lhs.arr, conditional.arr, rhs.arr) - return - - wrapper.replace_scalar(lhs.arr, conditional.arr, rhs) - - -def select(lhs: Array | int | float, rhs: Array | int | float, conditional: Array, /) -> None: - if isinstance(lhs, Array) and isinstance(rhs, Array): - wrapper.select(lhs.arr, conditional.arr, rhs.arr) - return - - if isinstance(lhs, Array) and not isinstance(rhs, Array): - wrapper.select_scalar_r(lhs.arr, conditional.arr, rhs) - return - - if not isinstance(lhs, Array) and isinstance(rhs, Array): - wrapper.select_scalar_l(lhs, conditional.arr, rhs.arr) - return - - raise TypeError("At least one array (lhr or rhs) must be of type af.Array.") - - -@afarray_as_array -def shift(array: Array, shape: tuple[int, ...], /) -> Array: - if len(shape) > 4: - raise ValueError("Max 4-dimensional arrays are supported.") - - return cast(Array, wrapper.shift(array.arr, *shape)) - - -@afarray_as_array -def tile(array: Array, shape: tuple[int, ...], /) -> Array: - if len(shape) > 4: - raise ValueError("Max 4-dimensional arrays are supported.") - - return cast(Array, wrapper.tile(array.arr, *shape)) - - -@afarray_as_array -def transpose(array: Array, /, *, conjugate: bool = False, inplace: bool = False) -> Array: - if inplace: - wrapper.transpose_inplace(array.arr, conjugate) - return array - - return cast(Array, wrapper.transpose(array.arr, conjugate)) diff --git a/arrayfire/library/computer_vision.py b/arrayfire/library/computer_vision.py index 14dab87..d4a0023 100644 --- a/arrayfire/library/computer_vision.py +++ b/arrayfire/library/computer_vision.py @@ -1,3 +1,5 @@ +__all__ = ["gloh", "orb", "sift", "dog", "fast", "harris", "susan", "hamming_matcher", "nearest_neighbour"] + from typing import cast import arrayfire_wrapper.lib as wrapper diff --git a/arrayfire/library/image_processing.py b/arrayfire/library/image_processing.py index c74b9ed..18c39c8 100644 --- a/arrayfire/library/image_processing.py +++ b/arrayfire/library/image_processing.py @@ -56,7 +56,7 @@ YCCStd, ) -from .array_management.creation import constant +from .array_functions import constant from .vector_algorithms import max as afmax from .vector_algorithms import min as afmin diff --git a/arrayfire/library/array_management/helpers.py b/arrayfire/library/utils.py similarity index 80% rename from arrayfire/library/array_management/helpers.py rename to arrayfire/library/utils.py index 872627d..3138aa7 100644 --- a/arrayfire/library/array_management/helpers.py +++ b/arrayfire/library/utils.py @@ -39,18 +39,3 @@ def cast(array: Array, dtype: Dtype, /) -> Array: An array containing the values from `array` after conversion to the specified `dtype`. """ return typing_cast(Array, wrapper.cast(array.arr, dtype)) - - -@afarray_as_array -def isinf(array: Array, /) -> Array: - return typing_cast(Array, wrapper.isinf(array.arr)) - - -@afarray_as_array -def isnan(array: Array, /) -> Array: - return typing_cast(Array, wrapper.isnan(array.arr)) - - -@afarray_as_array -def iszero(array: Array, /) -> Array: - return typing_cast(Array, wrapper.iszero(array.arr)) diff --git a/tests/array_management/test_array_creation.py b/tests/array_management/test_array_creation.py deleted file mode 100644 index dbcfef6..0000000 --- a/tests/array_management/test_array_creation.py +++ /dev/null @@ -1,71 +0,0 @@ -import pytest - -from arrayfire.dtypes import int64 -from arrayfire.library.array_management.creation import constant -from arrayfire.library.array_management.creation import range as afrange - -# Test cases for the constant function - - -def test_constant_1d() -> None: - result = constant(42, (5,)) - assert result.shape == (5,) - assert result.scalar() == 42 - - -def test_constant_2d() -> None: - result = constant(3.14, (3, 4)) - assert result.shape == (3, 4) - assert round(result.scalar(), 2) == 3.14 # type: ignore[arg-type] - - -def test_constant_3d() -> None: - result = constant(0, (2, 2, 2), dtype=int64) - assert result.shape == (2, 2, 2) - assert result.scalar() == 0 - assert result.dtype == int64 - - -def test_constant_default_shape() -> None: - result = constant(1.0) - assert result.shape == (1,) - assert result.scalar() == 1.0 - - -# TODO add error handling -# def test_constant_invalid_dtype() -> None: -# with pytest.raises(ValueError): -# constant(42, (3, 3), dtype="invalid_dtype") - - -# Test cases for the range function - - -def test_range_1d() -> None: - result = afrange((5,)) - assert result.shape == (5,) - - -def test_range_2d() -> None: - result = afrange((3, 4)) - assert result.shape == (3, 4) - - -def test_range_3d() -> None: - result = afrange((2, 2, 2)) - assert result.shape == (2, 2, 2) - - -def test_range_with_axis() -> None: - result = afrange((3, 4), axis=1) - assert result.shape == (3, 4) - - -def test_range_with_dtype() -> None: - result = afrange((4, 3), dtype=int64) - assert result.dtype == int64 - - -def test_range_with_invalid_axis() -> None: - with pytest.raises(ValueError): - afrange((2, 3, 4), axis=4) diff --git a/tests/array_management/test_array_modification.py b/tests/array_management/test_array_modification.py deleted file mode 100644 index 8487a98..0000000 --- a/tests/array_management/test_array_modification.py +++ /dev/null @@ -1,47 +0,0 @@ -from arrayfire import Array -from arrayfire.library import random -from arrayfire.library.array_management.modification import flat -from arrayfire.library.vector_algorithms import all_true - -# Test cases for the flat function - - -def test_flat_empty_array() -> None: - arr = Array() - flattened = flat(arr) - assert flattened.shape == () - - -def test_flat_1d() -> None: - arr = random.randu((5,)) - flattened = flat(arr) - assert flattened.shape == (5,) - assert all_true(flattened == arr, 0) - - -def test_flat_2d() -> None: - arr = random.randu((3, 2)) - flattened = flat(arr) - assert flattened.shape == (6,) - assert all_true(flattened == flat(arr), 0) - - -def test_flat_3d() -> None: - arr = random.randu((3, 2, 4)) - flattened = flat(arr) - assert flattened.shape == (24,) - assert all_true(flattened == flat(arr), 0) - - -def test_flat_4d() -> None: - arr = random.randu((3, 2, 4, 5)) - flattened = flat(arr) - assert flattened.shape == (120,) - assert all_true(flattened == flat(arr), 0) - - -def test_flat_large_array() -> None: - arr = random.randu((1000, 1000)) - flattened = flat(arr) - assert flattened.shape == (1000000,) - assert all_true(flattened == flat(arr), 0) diff --git a/tests/array_object/__init__.py b/tests/array_object/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/arrayfire/library/array_management/__init__.py b/tests/test_array_object/__init__.py similarity index 100% rename from arrayfire/library/array_management/__init__.py rename to tests/test_array_object/__init__.py diff --git a/tests/array_object/test_initialization.py b/tests/test_array_object/test_initialization.py old mode 100755 new mode 100644 similarity index 100% rename from tests/array_object/test_initialization.py rename to tests/test_array_object/test_initialization.py diff --git a/tests/array_object/test_methods.py b/tests/test_array_object/test_methods.py similarity index 100% rename from tests/array_object/test_methods.py rename to tests/test_array_object/test_methods.py diff --git a/tests/array_object/test_operator_overrides.py b/tests/test_array_object/test_operator_overrides.py similarity index 100% rename from tests/array_object/test_operator_overrides.py rename to tests/test_array_object/test_operator_overrides.py diff --git a/tests/array_management/__init__.py b/tests/test_library/__init__.py similarity index 100% rename from tests/array_management/__init__.py rename to tests/test_library/__init__.py diff --git a/tests/test_library/test_array_functions.py b/tests/test_library/test_array_functions.py new file mode 100644 index 0000000..48bb58e --- /dev/null +++ b/tests/test_library/test_array_functions.py @@ -0,0 +1,113 @@ +import pytest + +import arrayfire as af + +# Test cases for the af.constant function + + +def test_constant_1d() -> None: + result = af.constant(42, (5,)) + assert result.shape == (5,) + assert result.scalar() == 42 + + +def test_constant_2d() -> None: + result = af.constant(3.14, (3, 4)) + assert result.shape == (3, 4) + assert round(result.scalar(), 2) == 3.14 # type: ignore[arg-type] + + +def test_constant_3d() -> None: + result = af.constant(0, (2, 2, 2), dtype=af.int64) + assert result.shape == (2, 2, 2) + assert result.scalar() == 0 + assert result.dtype == af.int64 + + +def test_constant_default_shape() -> None: + result = af.constant(1.0) + assert result.shape == (1,) + assert result.scalar() == 1.0 + + +# TODO add error handling +# def test_constant_invalid_dtype() -> None: +# with pytest.raises(ValueError): +# af.constant(42, (3, 3), dtype="invalid_dtype") + + +# Test cases for the range function + + +def test_range_1d() -> None: + result = af.range((5,)) + assert result.shape == (5,) + + +def test_range_2d() -> None: + result = af.range((3, 4)) + assert result.shape == (3, 4) + + +def test_range_3d() -> None: + result = af.range((2, 2, 2)) + assert result.shape == (2, 2, 2) + + +def test_range_with_axis() -> None: + result = af.range((3, 4), axis=1) + assert result.shape == (3, 4) + + +def test_range_with_dtype() -> None: + result = af.range((4, 3), dtype=af.int64) + assert result.dtype == af.int64 + + +def test_range_with_invalid_axis() -> None: + with pytest.raises(ValueError): + af.range((2, 3, 4), axis=4) + + +# Test cases for the af.flat function + + +def test_flat_empty_array() -> None: + arr = af.Array() + flattened = af.flat(arr) + assert flattened.shape == () + + +def test_flat_1d() -> None: + arr = af.randu((5,)) + flattened = af.flat(arr) + assert flattened.shape == (5,) + assert af.all_true(flattened == arr, 0) + + +def test_flat_2d() -> None: + arr = af.randu((3, 2)) + flattened = af.flat(arr) + assert flattened.shape == (6,) + assert af.all_true(flattened == af.flat(arr), 0) + + +def test_flat_3d() -> None: + arr = af.randu((3, 2, 4)) + flattened = af.flat(arr) + assert flattened.shape == (24,) + assert af.all_true(flattened == af.flat(arr), 0) + + +def test_flat_4d() -> None: + arr = af.randu((3, 2, 4, 5)) + flattened = af.flat(arr) + assert flattened.shape == (120,) + assert af.all_true(flattened == af.flat(arr), 0) + + +def test_flat_large_array() -> None: + arr = af.randu((1000, 1000)) + flattened = af.flat(arr) + assert flattened.shape == (1000000,) + assert af.all_true(flattened == af.flat(arr), 0) diff --git a/tests/test_operators.py b/tests/test_library/test_operators.py old mode 100755 new mode 100644 similarity index 62% rename from tests/test_operators.py rename to tests/test_library/test_operators.py index 87115af..41f2ec9 --- a/tests/test_operators.py +++ b/tests/test_library/test_operators.py @@ -1,29 +1,28 @@ -from arrayfire import Array -from arrayfire.library import mathematical_functions +import arrayfire as af from tests._helpers import round_to class TestArithmeticOperators: def setup_method(self) -> None: - self.array1 = Array([1, 2, 3]) - self.array2 = Array([4, 5, 6]) + self.array1 = af.Array([1, 2, 3]) + self.array2 = af.Array([4, 5, 6]) def test_add(self) -> None: - res = mathematical_functions.add(self.array1, self.array2) + res = af.add(self.array1, self.array2) res_sum = self.array1 + self.array2 assert res.to_list() == res_sum.to_list() == [5, 7, 9] def test_sub(self) -> None: - res = mathematical_functions.sub(self.array1, self.array2) + res = af.sub(self.array1, self.array2) res_sum = self.array1 - self.array2 assert res.to_list() == res_sum.to_list() == [-3, -3, -3] def test_mul(self) -> None: - res = mathematical_functions.mul(self.array1, self.array2) + res = af.mul(self.array1, self.array2) res_product = self.array1 * self.array2 assert res.to_list() == res_product.to_list() == [4, 10, 18] def test_div(self) -> None: - res = mathematical_functions.div(self.array1, self.array2) + res = af.div(self.array1, self.array2) res_quotient = self.array1 / self.array2 assert round_to(res.to_list()) == round_to(res_quotient.to_list()) == [0.25, 0.4, 0.5] diff --git a/tests/test_random.py b/tests/test_library/test_random.py similarity index 100% rename from tests/test_random.py rename to tests/test_library/test_random.py diff --git a/tests/vector_algorithms/test_inclusive_scan_operations.py b/tests/test_library/test_vector_algorithms.py similarity index 59% rename from tests/vector_algorithms/test_inclusive_scan_operations.py rename to tests/test_library/test_vector_algorithms.py index e7ef32a..0bb935e 100644 --- a/tests/vector_algorithms/test_inclusive_scan_operations.py +++ b/tests/test_library/test_vector_algorithms.py @@ -1,3 +1,41 @@ +import pytest + +import arrayfire as af + +# Test reduction operators + + +@pytest.fixture +def true_array() -> af.Array: + return af.constant(1, (5, 5)) + + +@pytest.fixture +def false_array() -> af.Array: + arr = af.constant(1, (5, 5)) + arr[2, 2] = 0 # Set one element to False + return arr + + +# BUG af.Array.to_list() +# def test_all_true_with_axis(true_array: af.Array) -> None: +# result = va.all_true(true_array, axis=0) +# assert result.to_list() == [True, True, True, True, True] + + +# Test cases for the sum function + + +@pytest.fixture +def sample_array() -> af.Array: + return af.Array([1, 2, 3, 4]) + + +def test_sum_no_axis_no_nan_value(sample_array: af.Array) -> None: + result = af.sum(sample_array) + assert result == 10 # Sum of all elements is 1 + 2 + 3 + 4 = 10 + + # from typing import Union # import arrayfire as af # import pytest @@ -30,3 +68,5 @@ # array = af.randu(3, 4) # with pytest.raises(ValueError, match="Invalid axis"): # accum(array, axis=2) + +# TODO add more test cases diff --git a/tests/vector_algorithms/__init__.py b/tests/vector_algorithms/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/vector_algorithms/test_reduction_operations.py b/tests/vector_algorithms/test_reduction_operations.py deleted file mode 100644 index 6e3379f..0000000 --- a/tests/vector_algorithms/test_reduction_operations.py +++ /dev/null @@ -1,39 +0,0 @@ -import pytest - -from arrayfire import Array -from arrayfire.library import vector_algorithms as va -from arrayfire.library.array_management.creation import constant - - -@pytest.fixture -def true_array() -> Array: - return constant(1, (5, 5)) - - -@pytest.fixture -def false_array() -> Array: - arr = constant(1, (5, 5)) - arr[2, 2] = 0 # Set one element to False - return arr - - -# BUG Array.to_list() -# def test_all_true_with_axis(true_array: Array) -> None: -# result = va.all_true(true_array, axis=0) -# assert result.to_list() == [True, True, True, True, True] - - -# Test cases for the sum function - - -@pytest.fixture -def sample_array() -> Array: - return Array([1, 2, 3, 4]) - - -def test_sum_no_axis_no_nan_value(sample_array: Array) -> None: - result = va.sum(sample_array) - assert result == 10 # Sum of all elements is 1 + 2 + 3 + 4 = 10 - - -# TODO add more test cases From aad9790b43bff9d12982711434c6f11a585221a9 Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 4 Feb 2024 05:07:20 +0200 Subject: [PATCH 14/16] Remove implicit dtype from thick wrapper --- arrayfire/dtypes.py | 24 ----------------------- tests/test_dtypes.py | 45 +------------------------------------------- 2 files changed, 1 insertion(+), 68 deletions(-) diff --git a/arrayfire/dtypes.py b/arrayfire/dtypes.py index c817aff..9f3a8e0 100644 --- a/arrayfire/dtypes.py +++ b/arrayfire/dtypes.py @@ -93,30 +93,6 @@ ) -def implicit_dtype(number: int | float | py_bool | complex, array_dtype: Dtype) -> Dtype: - if isinstance(number, py_bool): - number_dtype = bool - elif isinstance(number, int): - number_dtype = int64 - elif isinstance(number, float): - number_dtype = float64 - elif isinstance(number, complex): - number_dtype = complex64 - else: - raise TypeError(f"{type(number)} is not supported and can not be converted to af.Dtype.") - - if not (array_dtype == float32 or array_dtype == complex32): - return number_dtype - - if number_dtype == float64: - return float32 - - if number_dtype == complex64: - return complex32 - - return number_dtype - - def c_api_value_to_dtype(value: int) -> Dtype: for dtype in supported_dtypes: if value == dtype.c_api_value: diff --git a/tests/test_dtypes.py b/tests/test_dtypes.py index ef50efa..c46e79f 100755 --- a/tests/test_dtypes.py +++ b/tests/test_dtypes.py @@ -2,20 +2,7 @@ import pytest -from arrayfire.dtypes import Dtype -from arrayfire.dtypes import bool as af_bool -from arrayfire.dtypes import ( - complex64, - float32, - float64, - implicit_dtype, - int16, - int32, - int64, - s16, - str_to_dtype, - uint16, -) +from arrayfire.dtypes import Dtype, float32, int16, int32, s16, str_to_dtype, uint16 def test_dtype_str_representation() -> None: @@ -36,36 +23,6 @@ def test_dtype_inequality() -> None: assert float32 != int16 -@pytest.mark.parametrize( - "number,array_dtype,expected_dtype", - [ - (1, int64, int64), - (1.0, float64, float64), - (1.0, float32, float32), - (True, float32, af_bool), - (1 + 2j, complex64, complex64), - ], -) -def test_implicit_dtype(number: int | float | bool | complex, array_dtype: Dtype, expected_dtype: Dtype) -> None: - result_dtype = implicit_dtype(number, array_dtype) - assert result_dtype == expected_dtype - - -def test_implicit_dtype_raises_error_invalid_array_dtype() -> None: - with pytest.raises(TypeError): - implicit_dtype([1], "invalid_dtype") # type: ignore[arg-type] - - -def test_implicit_dtype_raises_error_invalid_number_type() -> None: - with pytest.raises(TypeError): - implicit_dtype("invalid_number", int32) # type: ignore[arg-type] - - -def test_implicit_dtype_raises_error_invalid_combination() -> None: - with pytest.raises(TypeError): - implicit_dtype("invalid_number", float32) # type: ignore[arg-type] - - @pytest.mark.parametrize( "value,expected_dtype", [ From be5deb39351dc2f01a09ccf0ed85eba34877cf14 Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 4 Feb 2024 13:49:35 +0200 Subject: [PATCH 15/16] Fix math functions --- .gitignore | 1 + Makefile | 5 +- arrayfire/__init__.py | 2 + arrayfire/array_object.py | 87 ++-- arrayfire/library/mathematical_functions.py | 217 +++++----- .../test_mathematical_functions.py | 388 ++++++++++++++++++ tests/test_library/test_operators.py | 28 -- 7 files changed, 531 insertions(+), 197 deletions(-) create mode 100644 tests/test_library/test_mathematical_functions.py delete mode 100644 tests/test_library/test_operators.py diff --git a/.gitignore b/.gitignore index 83afdad..f1a00b9 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ __pycache__ # testing and continuous integration +coverage.xml .coverage .pytest_cache/ diff --git a/Makefile b/Makefile index 99f6198..6e2cbe2 100755 --- a/Makefile +++ b/Makefile @@ -37,10 +37,7 @@ typecheck : .PHONY : tests tests : - pytest --color=yes -v -rf --durations=40 \ - --cov-config=.coveragerc \ - --cov=$(SRC) \ - --cov-report=xml + pytest --color=yes -v -rf --durations=40 --cov-config=.coveragerc --cov=$(SRC) --cov-report=xml # Cleaning diff --git a/arrayfire/__init__.py b/arrayfire/__init__.py index d342569..36fbc3f 100755 --- a/arrayfire/__init__.py +++ b/arrayfire/__init__.py @@ -456,6 +456,7 @@ "logical_and", "logical_or", "logical_not", + "neg", ] @@ -511,6 +512,7 @@ minof, mod, mul, + neg, neq, pow, pow2, diff --git a/arrayfire/array_object.py b/arrayfire/array_object.py index 542dd22..683d205 100755 --- a/arrayfire/array_object.py +++ b/arrayfire/array_object.py @@ -164,7 +164,7 @@ def __neg__(self) -> Array: determined by Type Promotion Rules. """ - return _process_c_function(0, self, wrapper.sub) + return process_c_function(0, self, wrapper.sub) def __add__(self, other: int | float | Array, /) -> Array: """ @@ -183,7 +183,7 @@ def __add__(self, other: int | float | Array, /) -> Array: An array containing the element-wise sums. The returned array must have a data type determined by Type Promotion Rules. """ - return _process_c_function(self, other, wrapper.add) + return process_c_function(self, other, wrapper.add) def __sub__(self, other: int | float | Array, /) -> Array: """ @@ -205,7 +205,7 @@ def __sub__(self, other: int | float | Array, /) -> Array: An array containing the element-wise differences. The returned array must have a data type determined by Type Promotion Rules. """ - return _process_c_function(self, other, wrapper.sub) + return process_c_function(self, other, wrapper.sub) def __mul__(self, other: int | float | Array, /) -> Array: """ @@ -224,7 +224,7 @@ def __mul__(self, other: int | float | Array, /) -> Array: An array containing the element-wise products. The returned array must have a data type determined by Type Promotion Rules. """ - return _process_c_function(self, other, wrapper.mul) + return process_c_function(self, other, wrapper.mul) def __truediv__(self, other: int | float | Array, /) -> Array: """ @@ -251,7 +251,7 @@ def __truediv__(self, other: int | float | Array, /) -> Array: Specification-compliant libraries may choose to raise an error or return an array containing the element-wise results. If an array is returned, the array must have a real-valued floating-point data type. """ - return _process_c_function(self, other, wrapper.div) + return process_c_function(self, other, wrapper.div) def __floordiv__(self, other: int | float | Array, /) -> Array: # TODO @@ -281,7 +281,7 @@ def __mod__(self, other: int | float | Array, /) -> Array: - For input arrays which promote to an integer data type, the result of division by zero is unspecified and thus implementation-defined. """ - return _process_c_function(self, other, wrapper.mod) + return process_c_function(self, other, wrapper.mod) def __pow__(self, other: int | float | Array, /) -> Array: """ @@ -303,7 +303,7 @@ def __pow__(self, other: int | float | Array, /) -> Array: An array containing the element-wise results. The returned array must have a data type determined by Type Promotion Rules. """ - return _process_c_function(self, other, wrapper.pow) + return process_c_function(self, other, wrapper.pow) # Array Operators @@ -347,7 +347,7 @@ def __and__(self, other: int | bool | Array, /) -> Array: An array containing the element-wise results. The returned array must have a data type determined by Type Promotion Rules. """ - return _process_c_function(self, other, wrapper.bitand) + return process_c_function(self, other, wrapper.bitand) def __or__(self, other: int | bool | Array, /) -> Array: """ @@ -367,7 +367,7 @@ def __or__(self, other: int | bool | Array, /) -> Array: An array containing the element-wise results. The returned array must have a data type determined by Type Promotion Rules. """ - return _process_c_function(self, other, wrapper.bitor) + return process_c_function(self, other, wrapper.bitor) def __xor__(self, other: int | bool | Array, /) -> Array: """ @@ -387,7 +387,7 @@ def __xor__(self, other: int | bool | Array, /) -> Array: An array containing the element-wise results. The returned array must have a data type determined by Type Promotion Rules. """ - return _process_c_function(self, other, wrapper.bitxor) + return process_c_function(self, other, wrapper.bitxor) def __lshift__(self, other: int | Array, /) -> Array: """ @@ -407,7 +407,7 @@ def __lshift__(self, other: int | Array, /) -> Array: out : Array An array containing the element-wise results. The returned array must have the same data type as self. """ - return _process_c_function(self, other, wrapper.bitshiftl) + return process_c_function(self, other, wrapper.bitshiftl) def __rshift__(self, other: int | Array, /) -> Array: """ @@ -427,7 +427,7 @@ def __rshift__(self, other: int | Array, /) -> Array: out : Array An array containing the element-wise results. The returned array must have the same data type as self. """ - return _process_c_function(self, other, wrapper.bitshiftr) + return process_c_function(self, other, wrapper.bitshiftr) # Comparison Operators @@ -448,7 +448,7 @@ def __lt__(self, other: int | float | Array, /) -> Array: out : Array An array containing the element-wise results. The returned array must have a data type of bool. """ - return _process_c_function(self, other, wrapper.lt) + return process_c_function(self, other, wrapper.lt) def __le__(self, other: int | float | Array, /) -> Array: """ @@ -467,7 +467,7 @@ def __le__(self, other: int | float | Array, /) -> Array: out : Array An array containing the element-wise results. The returned array must have a data type of bool. """ - return _process_c_function(self, other, wrapper.le) + return process_c_function(self, other, wrapper.le) def __gt__(self, other: int | float | Array, /) -> Array: """ @@ -486,7 +486,7 @@ def __gt__(self, other: int | float | Array, /) -> Array: out : Array An array containing the element-wise results. The returned array must have a data type of bool. """ - return _process_c_function(self, other, wrapper.gt) + return process_c_function(self, other, wrapper.gt) def __ge__(self, other: int | float | Array, /) -> Array: """ @@ -505,7 +505,7 @@ def __ge__(self, other: int | float | Array, /) -> Array: out : Array An array containing the element-wise results. The returned array must have a data type of bool. """ - return _process_c_function(self, other, wrapper.ge) + return process_c_function(self, other, wrapper.ge) def __eq__(self, other: int | float | bool | Array, /) -> Array: # type: ignore[override] """ @@ -524,7 +524,7 @@ def __eq__(self, other: int | float | bool | Array, /) -> Array: # type: ignore out : Array An array containing the element-wise results. The returned array must have a data type of bool. """ - return _process_c_function(self, other, wrapper.eq) + return process_c_function(self, other, wrapper.eq) def __ne__(self, other: int | float | bool | Array, /) -> Array: # type: ignore[override] """ @@ -543,7 +543,7 @@ def __ne__(self, other: int | float | bool | Array, /) -> Array: # type: ignore out : Array An array containing the element-wise results. The returned array must have a data type of bool. """ - return _process_c_function(self, other, wrapper.neq) + return process_c_function(self, other, wrapper.neq) # Reflected Arithmetic Operators @@ -551,25 +551,25 @@ def __radd__(self, other: Array, /) -> Array: """ Return other + self. """ - return _process_c_function(other, self, wrapper.add) + return process_c_function(other, self, wrapper.add) def __rsub__(self, other: Array, /) -> Array: """ Return other - self. """ - return _process_c_function(other, self, wrapper.sub) + return process_c_function(other, self, wrapper.sub) def __rmul__(self, other: Array, /) -> Array: """ Return other * self. """ - return _process_c_function(other, self, wrapper.mul) + return process_c_function(other, self, wrapper.mul) def __rtruediv__(self, other: Array, /) -> Array: """ Return other / self. """ - return _process_c_function(other, self, wrapper.div) + return process_c_function(other, self, wrapper.div) def __rfloordiv__(self, other: Array, /) -> Array: # TODO @@ -579,13 +579,13 @@ def __rmod__(self, other: Array, /) -> Array: """ Return other % self. """ - return _process_c_function(other, self, wrapper.mod) + return process_c_function(other, self, wrapper.mod) def __rpow__(self, other: Array, /) -> Array: """ Return other ** self. """ - return _process_c_function(other, self, wrapper.pow) + return process_c_function(other, self, wrapper.pow) # Reflected Array Operators @@ -599,31 +599,31 @@ def __rand__(self, other: Array, /) -> Array: """ Return other & self. """ - return _process_c_function(other, self, wrapper.bitand) + return process_c_function(other, self, wrapper.bitand) def __ror__(self, other: Array, /) -> Array: """ Return other | self. """ - return _process_c_function(other, self, wrapper.bitor) + return process_c_function(other, self, wrapper.bitor) def __rxor__(self, other: Array, /) -> Array: """ Return other ^ self. """ - return _process_c_function(other, self, wrapper.bitxor) + return process_c_function(other, self, wrapper.bitxor) def __rlshift__(self, other: Array, /) -> Array: """ Return other << self. """ - return _process_c_function(other, self, wrapper.bitshiftl) + return process_c_function(other, self, wrapper.bitshiftl) def __rrshift__(self, other: Array, /) -> Array: """ Return other >> self. """ - return _process_c_function(other, self, wrapper.bitshiftr) + return process_c_function(other, self, wrapper.bitshiftr) # In-place Arithmetic Operators @@ -632,25 +632,25 @@ def __iadd__(self, other: int | float | Array, /) -> Array: """ Return self += other. """ - return _process_c_function(self, other, wrapper.add) + return process_c_function(self, other, wrapper.add) def __isub__(self, other: int | float | Array, /) -> Array: """ Return self -= other. """ - return _process_c_function(self, other, wrapper.sub) + return process_c_function(self, other, wrapper.sub) def __imul__(self, other: int | float | Array, /) -> Array: """ Return self *= other. """ - return _process_c_function(self, other, wrapper.mul) + return process_c_function(self, other, wrapper.mul) def __itruediv__(self, other: int | float | Array, /) -> Array: """ Return self /= other. """ - return _process_c_function(self, other, wrapper.div) + return process_c_function(self, other, wrapper.div) def __ifloordiv__(self, other: int | float | Array, /) -> Array: # TODO @@ -660,13 +660,13 @@ def __imod__(self, other: int | float | Array, /) -> Array: """ Return self %= other. """ - return _process_c_function(self, other, wrapper.mod) + return process_c_function(self, other, wrapper.mod) def __ipow__(self, other: int | float | Array, /) -> Array: """ Return self **= other. """ - return _process_c_function(self, other, wrapper.pow) + return process_c_function(self, other, wrapper.pow) # In-place Array Operators @@ -680,31 +680,31 @@ def __iand__(self, other: int | bool | Array, /) -> Array: """ Return self &= other. """ - return _process_c_function(self, other, wrapper.bitand) + return process_c_function(self, other, wrapper.bitand) def __ior__(self, other: int | bool | Array, /) -> Array: """ Return self |= other. """ - return _process_c_function(self, other, wrapper.bitor) + return process_c_function(self, other, wrapper.bitor) def __ixor__(self, other: int | bool | Array, /) -> Array: """ Return self ^= other. """ - return _process_c_function(self, other, wrapper.bitxor) + return process_c_function(self, other, wrapper.bitxor) def __ilshift__(self, other: int | Array, /) -> Array: """ Return self <<= other. """ - return _process_c_function(self, other, wrapper.bitshiftl) + return process_c_function(self, other, wrapper.bitshiftl) def __irshift__(self, other: int | Array, /) -> Array: """ Return self >>= other. """ - return _process_c_function(self, other, wrapper.bitshiftr) + return process_c_function(self, other, wrapper.bitshiftr) # Methods @@ -821,7 +821,10 @@ def __repr__(self) -> str: return _array_as_str(self) def __del__(self) -> None: - if not self._arr.value: + if not hasattr(self._arr, "value"): + return + + if self._arr.value == 0: return wrapper.release_array(self._arr) @@ -1116,7 +1119,7 @@ def _metadata_string(dtype: Dtype, dims: tuple[int, ...] | None = None) -> str: @afarray_as_array -def _process_c_function(lhs: int | float | Array, rhs: int | float | Array, c_function: Any) -> Array: +def process_c_function(lhs: int | float | Array, rhs: int | float | Array, c_function: Any) -> Array: if isinstance(lhs, Array) and isinstance(rhs, Array): lhs_array = lhs.arr rhs_array = rhs.arr diff --git a/arrayfire/library/mathematical_functions.py b/arrayfire/library/mathematical_functions.py index f0b802f..bc7b970 100644 --- a/arrayfire/library/mathematical_functions.py +++ b/arrayfire/library/mathematical_functions.py @@ -66,46 +66,41 @@ "cos", "sin", "tan", + "neg", ] from typing import cast import arrayfire_wrapper.lib as wrapper -from arrayfire import Array -from arrayfire.array_object import afarray_as_array +from arrayfire.array_object import Array, afarray_as_array, process_c_function -@afarray_as_array -def add(x1: Array, x2: Array, /) -> Array: - return cast(Array, wrapper.add(x1.arr, x2.arr)) +def add(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.add) -@afarray_as_array -def sub(x1: Array, x2: Array, /) -> Array: - return wrapper.sub(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def sub(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.sub) -@afarray_as_array -def mul(x1: Array, x2: Array, /) -> Array: - return wrapper.mul(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def mul(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.mul) -@afarray_as_array -def div(x1: Array, x2: Array, /) -> Array: - return wrapper.div(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def div(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.div) -@afarray_as_array def mod(x1: int | float | Array, x2: int | float | Array, /) -> Array: """ Calculate the modulus of two arrays or a scalar and an array. Parameters ---------- - x1 : int |float |Array + x1 : int | float | Array The first array or scalar operand. - x2 : int |float |Array + x2 : int | float | Array The second array or scalar operand. Returns @@ -118,343 +113,319 @@ def mod(x1: int | float | Array, x2: int | float | Array, /) -> Array: ValueError If both operands are scalars or if the arrays' shapes do not match. """ + return process_c_function(x1, x2, wrapper.mod) - x1_ = x1.arr if isinstance(x1, Array) else x1 - x2_ = x2.arr if isinstance(x2, Array) else x2 - - result = wrapper.mod(x1_, x2_) - return cast(Array, result) - -@afarray_as_array def pow(x1: int | float | Array, x2: int | float | Array, /) -> Array: - x1_ = x1.arr if isinstance(x1, Array) else x1 - x2_ = x2.arr if isinstance(x2, Array) else x2 - - return cast(Array, wrapper.pow(x1_, x2_)) + return process_c_function(x1, x2, wrapper.pow) @afarray_as_array def bitnot(x: Array, /) -> Array: - return wrapper.bitnot(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.bitnot(x.arr)) -@afarray_as_array -def bitand(x1: Array, x2: Array, /) -> Array: - return wrapper.bitand(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def bitand(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.bitand) -@afarray_as_array -def bitor(x1: Array, x2: Array, /) -> Array: - return wrapper.bitor(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def bitor(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.bitor) -@afarray_as_array -def bitxor(x1: Array, x2: Array, /) -> Array: - return wrapper.bitxor(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def bitxor(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.bitxor) -@afarray_as_array -def bitshiftl(x1: Array, x2: Array, /) -> Array: - return wrapper.bitshiftl(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def bitshiftl(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.bitshiftl) -@afarray_as_array -def bitshiftr(x1: Array, x2: Array, /) -> Array: - return wrapper.bitshiftr(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def bitshiftr(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.bitshiftr) -@afarray_as_array -def lt(x1: Array, x2: Array, /) -> Array: - return wrapper.lt(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def lt(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.lt) -@afarray_as_array -def le(x1: Array, x2: Array, /) -> Array: - return wrapper.le(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def le(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.le) -@afarray_as_array -def gt(x1: Array, x2: Array, /) -> Array: - return wrapper.gt(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def gt(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.gt) -@afarray_as_array -def ge(x1: Array, x2: Array, /) -> Array: - return wrapper.ge(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def ge(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.ge) -@afarray_as_array -def eq(x1: Array, x2: Array, /) -> Array: - return wrapper.eq(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def eq(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.eq) -@afarray_as_array -def neq(x1: Array, x2: Array, /) -> Array: - return wrapper.neq(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def neq(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.neq) @afarray_as_array def clamp(x: Array, /, lo: float, hi: float) -> Array: + # TODO return NotImplemented -@afarray_as_array def minof(x1: int | float | Array, x2: int | float | Array, /) -> Array: - return cast(Array, wrapper.minof(x1.arr, x2.arr)) + return process_c_function(x1, x2, wrapper.minof) -@afarray_as_array def maxof(x1: int | float | Array, x2: int | float | Array, /) -> Array: - return wrapper.maxof(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + return process_c_function(x1, x2, wrapper.maxof) -@afarray_as_array def rem(x1: int | float | Array, x2: int | float | Array, /) -> Array: - return wrapper.rem(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + return process_c_function(x1, x2, wrapper.rem) @afarray_as_array def abs(x: Array, /) -> Array: - return wrapper.abs_(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.abs_(x.arr)) @afarray_as_array def arg(x: Array, /) -> Array: - return wrapper.arg(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.arg(x.arr)) @afarray_as_array def sign(x: Array, /) -> Array: - return wrapper.sign(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.sign(x.arr)) @afarray_as_array def round(x: Array, /) -> Array: - return wrapper.round_(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.round_(x.arr)) @afarray_as_array def trunc(x: Array, /) -> Array: - return wrapper.trunc(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.trunc(x.arr)) @afarray_as_array def floor(x: Array, /) -> Array: - return wrapper.floor(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.floor(x.arr)) @afarray_as_array def ceil(x: Array, /) -> Array: - return wrapper.ceil(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.ceil(x.arr)) @afarray_as_array def hypot(x1: int | float | Array, x2: int | float | Array, /) -> Array: - return wrapper.hypot(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + return process_c_function(x1, x2, wrapper.hypot) @afarray_as_array def sin(x: Array, /) -> Array: - return wrapper.sin(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.sin(x.arr)) @afarray_as_array def cos(x: Array, /) -> Array: - return wrapper.cos(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.cos(x.arr)) @afarray_as_array def tan(x: Array, /) -> Array: - return wrapper.tan(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.tan(x.arr)) @afarray_as_array def asin(x: Array, /) -> Array: - return wrapper.asin(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.asin(x.arr)) @afarray_as_array def acos(x: Array, /) -> Array: - return wrapper.acos(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.acos(x.arr)) @afarray_as_array def atan(x: Array, /) -> Array: - return wrapper.atan(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.atan(x.arr)) -@afarray_as_array def atan2(x1: int | float | Array, x2: int | float | Array, /) -> Array: - return wrapper.atan2(x1.arr, x2.arr) # type: ignore[arg-type, return-value] + return process_c_function(x1, x2, wrapper.atan2) @afarray_as_array -def cplx(x1: int | float | Array, x2: int | float | Array | None, /) -> Array: +def cplx(x1: int | float | Array, /, x2: int | float | Array | None = None) -> Array: if x2 is None: + if not isinstance(x1, Array): + raise TypeError("x1 can not be int or tuple when x2 is None.") return cast(Array, wrapper.cplx(x1.arr)) else: - return cast(Array, wrapper.cplx2(x1.arr, x2.arr)) + return process_c_function(x1, x2, wrapper.cplx2) @afarray_as_array def real(x: Array, /) -> Array: - return wrapper.real(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.real(x.arr)) @afarray_as_array def imag(x: Array, /) -> Array: - return wrapper.imag(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.imag(x.arr)) @afarray_as_array def conjg(x: Array, /) -> Array: - return wrapper.conjg(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.conjg(x.arr)) @afarray_as_array def sinh(x: Array, /) -> Array: - return wrapper.sinh(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.sinh(x.arr)) @afarray_as_array def cosh(x: Array, /) -> Array: - return wrapper.cosh(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.cosh(x.arr)) @afarray_as_array def tanh(x: Array, /) -> Array: - return wrapper.tanh(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.tanh(x.arr)) @afarray_as_array def asinh(x: Array, /) -> Array: - return wrapper.asinh(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.asinh(x.arr)) @afarray_as_array def acosh(x: Array, /) -> Array: - return wrapper.acosh(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.acosh(x.arr)) @afarray_as_array def atanh(x: Array, /) -> Array: - return wrapper.atanh(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.atanh(x.arr)) -@afarray_as_array def root(x1: int | float | Array, x2: int | float | Array, /) -> Array: - return wrapper.root(x1.arr, x2.arr) + return process_c_function(x1, x2, wrapper.root) @afarray_as_array def pow2(x: Array, /) -> Array: - return wrapper.pow2(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.pow2(x.arr)) @afarray_as_array def sigmoid(x: Array, /) -> Array: - return wrapper.sigmoid(x.arr) + return cast(Array, wrapper.sigmoid(x.arr)) @afarray_as_array def exp(x: Array, /) -> Array: - return wrapper.exp(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.exp(x.arr)) @afarray_as_array def expm1(x: Array, /) -> Array: - return wrapper.expm1(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.expm1(x.arr)) @afarray_as_array def erf(x: Array, /) -> Array: - return wrapper.erf(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.erf(x.arr)) @afarray_as_array def erfc(x: Array, /) -> Array: - return wrapper.erfc(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.erfc(x.arr)) @afarray_as_array def log(x: Array, /) -> Array: - return wrapper.log(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.log(x.arr)) @afarray_as_array def log1p(x: Array, /) -> Array: - return wrapper.log1p(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.log1p(x.arr)) @afarray_as_array def log10(x: Array, /) -> Array: - return wrapper.log10(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.log10(x.arr)) @afarray_as_array def log2(x: Array, /) -> Array: - return wrapper.log2(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.log2(x.arr)) @afarray_as_array def sqrt(x: Array, /) -> Array: - return wrapper.sqrt(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.sqrt(x.arr)) @afarray_as_array def rsqrt(x: Array, /) -> Array: - return wrapper.rsqrt(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.rsqrt(x.arr)) @afarray_as_array def cbrt(x: Array, /) -> Array: - return wrapper.cbrt(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.cbrt(x.arr)) @afarray_as_array def factorial(x: Array, /) -> Array: - return wrapper.factorial(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.factorial(x.arr)) @afarray_as_array def tgamma(x: Array, /) -> Array: - return wrapper.tgamma(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.tgamma(x.arr)) @afarray_as_array def lgamma(x: Array, /) -> Array: - return wrapper.lgamma(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.lgamma(x.arr)) @afarray_as_array def iszero(x: Array, /) -> Array: - return wrapper.iszero(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.iszero(x.arr)) @afarray_as_array def isinf(x: Array, /) -> Array: - return wrapper.isinf(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.isinf(x.arr)) @afarray_as_array def isnan(x: Array, /) -> Array: - return wrapper.isnan(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.isnan(x.arr)) -@afarray_as_array -def logical_and(x1: Array, x2: Array, /) -> Array: - return wrapper.and_(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def logical_and(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.and_) -@afarray_as_array -def logical_or(x1: Array, x2: Array, /) -> Array: - return wrapper.or_(x1.arr, x2.arr) # type: ignore[arg-type, return-value] +def logical_or(x1: Array | int | float, x2: Array | int | float, /) -> Array: + return process_c_function(x1, x2, wrapper.or_) @afarray_as_array def logical_not(x: Array, /) -> Array: - return wrapper.not_(x.arr) # type: ignore[arg-type, return-value] + return cast(Array, wrapper.not_(x.arr)) @afarray_as_array diff --git a/tests/test_library/test_mathematical_functions.py b/tests/test_library/test_mathematical_functions.py new file mode 100644 index 0000000..de98883 --- /dev/null +++ b/tests/test_library/test_mathematical_functions.py @@ -0,0 +1,388 @@ +import math + +import arrayfire as af +from tests._helpers import round_to + + +class TestArithmeticOperators: + def setup_method(self) -> None: + self.array1 = af.Array([1, 2, 3]) + self.array2 = af.Array([4, 5, 6]) + self.scalar = 2 + + def test_add(self) -> None: + res = af.add(self.array1, self.array2) + res_sum = self.array1 + self.array2 + assert res.to_list() == res_sum.to_list() == [5, 7, 9] + + def test_sub(self) -> None: + res = af.sub(self.array1, self.array2) + res_sum = self.array1 - self.array2 + assert res.to_list() == res_sum.to_list() == [-3, -3, -3] + + def test_mul(self) -> None: + res = af.mul(self.array1, self.array2) + res_product = self.array1 * self.array2 + assert res.to_list() == res_product.to_list() == [4, 10, 18] + + def test_div(self) -> None: + res = af.div(self.array1, self.array2) + res_quotient = self.array1 / self.array2 + assert round_to(res.to_list()) == round_to(res_quotient.to_list()) == [0.25, 0.4, 0.5] + + def test_mod(self) -> None: + res = af.mod(self.array1, self.array2) + expected = [1 % 4, 2 % 5, 3 % 6] + assert res.to_list() == expected + + res_scalar = af.mod(self.scalar, self.array1) + expected_scalar = [2 % 1, 2 % 2, 2 % 3] + assert res_scalar.to_list() == expected_scalar + + def test_pow(self) -> None: + res = af.pow(self.array1, self.array2) + expected = [1**4, 2**5, 3**6] + assert res.to_list() == expected + + # BUG + # OSError: exception: access violation reading 0xFFFFFFFFFFFFFFFF + + # def test_bitand(self) -> None: + # res_and = af.bitand(self.array1, self.array2) + # expected_and = [1 & 4, 2 & 5, 3 & 6] + # assert res_and.to_list() == expected_and + + # def test_bitor(self) -> None: + # res_or = af.bitor(self.array1, self.array2) + # expected_or = [1 | 4, 2 | 5, 3 | 6] + # assert res_or.to_list() == expected_or + + # def test_bitxor(self) -> None: + # res_xor = af.bitxor(self.array1, self.array2) + # expected_xor = [1 ^ 4, 2 ^ 5, 3 ^ 6] + # assert res_xor.to_list() == expected_xor + + # def test_bitshiftl(self) -> None: + # # bitshiftl and bitshiftr assume the second array is for shift amounts + # res_shiftl = af.bitshiftl(self.array1, af.Array([1, 1, 1])) + # expected_shiftl = [1 << 1, 2 << 1, 3 << 1] + # assert res_shiftl.to_list() == expected_shiftl + + # def test_bitshiftr(self) -> None: + # res_shiftr = af.bitshiftr(self.array1, af.Array([1, 1, 1])) + # expected_shiftr = [1 >> 1, 2 >> 1, 3 >> 1] + # assert res_shiftr.to_list() == expected_shiftr + + def test_le(self) -> None: + # Less than or equal to + res_le = af.le(self.array1, self.array2) + expected_le = [1 <= 4, 2 <= 5, 3 <= 6] + assert res_le.to_list() == expected_le + + # Scalar comparison + res_scalar_le = af.le(self.array1, 2) + expected_scalar_le = [x <= 2 for x in [1, 2, 3]] + assert res_scalar_le.to_list() == expected_scalar_le + + def test_lt(self) -> None: + # Less than + res_lt = af.lt(self.array1, self.array2) + expected_lt = [1 < 4, 2 < 5, 3 < 6] + assert res_lt.to_list() == expected_lt + + # Scalar comparison + res_scalar_lt = af.lt(self.array1, 2) + expected_scalar_lt = [x < 2 for x in [1, 2, 3]] + assert res_scalar_lt.to_list() == expected_scalar_lt + + def test_gt(self) -> None: + # Greater than + res_gt = af.gt(self.array1, self.array2) + expected_gt = [1 > 4, 2 > 5, 3 > 6] + assert res_gt.to_list() == expected_gt + + # Scalar comparison + res_scalar_gt = af.gt(self.array1, 2) + expected_scalar_gt = [x > 2 for x in [1, 2, 3]] + assert res_scalar_gt.to_list() == expected_scalar_gt + + def test_ge(self) -> None: + # Greater than or equal to + res_ge = af.ge(self.array1, self.array2) + expected_ge = [1 >= 4, 2 >= 5, 3 >= 6] + assert res_ge.to_list() == expected_ge + + # Scalar comparison + res_scalar_ge = af.ge(self.array1, 2) + expected_scalar_ge = [x >= 2 for x in [1, 2, 3]] + assert res_scalar_ge.to_list() == expected_scalar_ge + + def test_eq(self) -> None: + # Equal to + res_eq = af.eq(self.array1, self.array2) + expected_eq = [1 == 4, 2 == 5, 3 == 6] + assert res_eq.to_list() == expected_eq + + # Scalar comparison + res_scalar_eq = af.eq(self.array1, 2) + expected_scalar_eq = [x == 2 for x in [1, 2, 3]] + assert res_scalar_eq.to_list() == expected_scalar_eq + + def test_neq(self) -> None: + # Not equal to + res_neq = af.neq(self.array1, self.array2) + expected_neq = [1 != 4, 2 != 5, 3 != 6] + assert res_neq.to_list() == expected_neq + + # Scalar comparison + res_scalar_neq = af.neq(self.array1, 2) + expected_scalar_neq = [x != 2 for x in [1, 2, 3]] + assert res_scalar_neq.to_list() == expected_scalar_neq + + def test_min_max_operations(self) -> None: + # minof + res_min = af.minof(self.array1, self.array2) + expected_min = [min(1, 4), min(2, 5), min(3, 6)] + assert res_min.to_list() == expected_min + + # maxof + res_max = af.maxof(self.array1, self.array2) + expected_max = [max(1, 4), max(2, 5), max(3, 6)] + assert res_max.to_list() == expected_max + + def test_rem(self) -> None: + res = af.rem(self.array1, self.array2) + expected = [1 % 4, 2 % 5, 3 % 6] # Similar to mod but using rem function + assert res.to_list() == expected + + def test_abs(self) -> None: + negative_array = af.Array([-1, -2, -3]) + res = af.abs(negative_array) + expected = [abs(-1), abs(-2), abs(-3)] + assert res.to_list() == expected + + def test_sign(self) -> None: + array = af.Array([-1, 0, 1]) + res = af.sign(array) + expected = [1, 0, 0] + assert res.to_list() == expected + + def test_round(self) -> None: + array = af.Array([1.2, 2.5, 3.6]) + res = af.round(array) + expected = [1, 3, 4] # NOTE: Python's round may behave differently + assert res.to_list() == expected + + def test_trunc(self) -> None: + array = af.Array([1.9, -2.8, 3.7]) + res = af.trunc(array) + expected = [1, -2, 3] + assert res.to_list() == expected + + def test_floor(self) -> None: + array = af.Array([1.2, -2.5, 3.8]) + res = af.floor(array) + expected = [1, -3, 3] + assert res.to_list() == expected + + def test_ceil(self) -> None: + array = af.Array([1.2, -2.5, 3.8]) + res = af.ceil(array) + expected = [2, -2, 4] + assert res.to_list() == expected + + # BUG + # def test_hypot(self) -> None: + # res = af.hypot(self.array1, self.array2) + # expected = [(1**2 + 4**2) ** 0.5, (2**2 + 5**2) ** 0.5, (3**2 + 6**2) ** 0.5] + # assert round_to(res.to_list()) == round_to(expected) + + def test_sin(self) -> None: + # Sin + res_sin = af.sin(af.Array([0, math.pi / 4, math.pi / 2])) + expected_sin = [0, 1 / (2**0.5), 1] + assert round_to(res_sin.to_list()) == round_to(expected_sin) # type: ignore[arg-type] + + def test_cos(self) -> None: + # Cos + res_cos = af.cos(af.Array([0, math.pi / 4, math.pi / 2])) + expected_cos = [1, 1 / (2**0.5), 0] + assert round_to(res_cos.to_list()) == round_to(expected_cos) # type: ignore[arg-type] + + def test_tan(self) -> None: + # Tan + res_tan = af.tan(af.Array([0, math.pi / 4, math.pi / 3])) + expected_tan = [0, 1, 1.732] + assert round_to(res_tan.to_list()) == round_to(expected_tan) # type: ignore[arg-type] + + def test_asin(self) -> None: + # ASin + res_asin = af.asin(af.Array([0, 0.5, 1])) + expected_asin = [0, math.pi / 6, math.pi / 2] + assert round_to(res_asin.to_list()) == round_to(expected_asin) # type: ignore[arg-type] + + def test_acos(self) -> None: + # ACos + res_acos = af.acos(af.Array([0, 0.5, 1])) + expected_acos = [math.pi / 2, math.pi / 3, 0] + assert round_to(res_acos.to_list()) == round_to(expected_acos) # type: ignore[arg-type] + + def test_atan(self) -> None: + # ATan + res_atan = af.atan(af.Array([-1, 0, 1])) + expected_atan = [-math.pi / 4, 0, math.pi / 4] # Adjust expected values accordingly + assert round_to(res_atan.to_list()) == round_to(expected_atan) # type: ignore[arg-type] + + def test_atan2(self) -> None: + res = af.atan2(self.array1, self.array2) + expected = [math.atan2(1, 4), math.atan2(2, 5), math.atan2(3, 6)] + assert round_to(res.to_list()) == round_to(expected) # type: ignore[arg-type] + + def test_sinh(self) -> None: + # Sinh + array = af.Array([0, 1, -1]) + res_sinh = af.sinh(array) + expected_sinh = [ + 0, + (math.e - 1 / math.e) / 2, + -(math.e - 1 / math.e) / 2, + ] + assert round_to(res_sinh.to_list()) == round_to(expected_sinh) # type: ignore[arg-type] + + def test_cosh(self) -> None: + # Cosh + array = af.Array([0, 1, -1]) + res_cosh = af.cosh(array) + expected_cosh = [1, (math.e + 1 / math.e) / 2, (math.e + 1 / math.e) / 2] + assert round_to(res_cosh.to_list()) == round_to(expected_cosh) # type: ignore[arg-type] + + def test_tanh(self) -> None: + # Tanh + array = af.Array([0, 1, -1]) + res_tanh = af.tanh(array) + expected_tanh = [0, (math.e**2 - 1) / (math.e**2 + 1), -(math.e**2 - 1) / (math.e**2 + 1)] + assert round_to(res_tanh.to_list()) == round_to(expected_tanh) # type: ignore[arg-type] + + # BUG + # root(3, 2) != math.sqrt(3) == 3**0.5 + # def test_root(self) -> None: + # res = af.root(self.array1, 2) + # expected = [1**0.5, 2**0.5, 3**0.5] + # assert round_to(res.to_list()) == round_to(expected) # type: ignore[arg-type] + + def test_pow2(self) -> None: + res = af.pow2(self.array1) + expected = [2**1, 2**2, 2**3] + assert res.to_list() == expected + + def test_sigmoid(self) -> None: + res = af.sigmoid(self.array1) + expected = [1 / (1 + math.e**-x) for x in [1, 2, 3]] + assert round_to(res.to_list()) == round_to(expected) # type: ignore[arg-type] + + def test_exponential_functions(self) -> None: + # Exp + res_exp = af.exp(self.array1) + expected_exp = [math.e**1, math.e**2, math.e**3] + assert round_to(res_exp.to_list()) == round_to(expected_exp) # type: ignore[arg-type] + + # Expm1 + res_expm1 = af.expm1(self.array1) + expected_expm1 = [(math.e**x) - 1 for x in [1, 2, 3]] + assert round_to(res_expm1.to_list()) == round_to(expected_expm1) # type: ignore[arg-type] + + def test_error_functions(self) -> None: + array_values = [1, 2, 3] # from self.array1 + expected_erf = [math.erf(x) for x in array_values] + expected_erfc = [math.erfc(x) for x in array_values] + + # Erf + res_erf = af.erf(af.Array(array_values)) # type: ignore[arg-type] + assert round_to(res_erf.to_list()) == round_to(expected_erf) # type: ignore[arg-type] + + # Erfc + res_erfc = af.erfc(af.Array(array_values)) # type: ignore[arg-type] + assert round_to(res_erfc.to_list()) == round_to(expected_erfc) # type: ignore[arg-type] + + def test_logarithmic_functions(self) -> None: + # Log + res_log = af.log(self.array1) + expected_log = [math.log(x) for x in [1, 2, 3]] + assert round_to(res_log.to_list()) == round_to(expected_log) # type: ignore[arg-type] + + # Log1p + res_log1p = af.log1p(self.array1) + expected_log1p = [math.log(x + 1) for x in [1, 2, 3]] + assert round_to(res_log1p.to_list()) == round_to(expected_log1p) # type: ignore[arg-type] + + # Log10 + array = af.Array([1, 10, 100]) + res_log10 = af.log10(array) + expected_log10 = [math.log10(x) for x in [1, 10, 100]] # Using Python's log10 from math module + assert round_to(res_log10.to_list()) == round_to(expected_log10) # type: ignore[arg-type] + + # Log2 + array = af.Array([1, 2, 4]) + res_log2 = af.log2(array) + expected_log2 = [math.log2(x) for x in [1, 2, 4]] # Using Python's log2 from math module + assert round_to(res_log2.to_list()) == round_to(expected_log2) # type: ignore[arg-type] + + def test_sqrt(self) -> None: + # Sqrt + res_sqrt = af.sqrt(self.array1) + expected_sqrt = [x**0.5 for x in [1, 2, 3]] + assert round_to(res_sqrt.to_list()) == round_to(expected_sqrt) # type: ignore[arg-type] + + def test_cbrt(self) -> None: + # Cbrt + res_cbrt = af.cbrt(self.array1) + expected_cbrt = [x ** (1 / 3) for x in [1, 2, 3]] + assert round_to(res_cbrt.to_list()) == round_to(expected_cbrt) # type: ignore[arg-type] + + def test_factorial(self) -> None: + # Factorial + res_factorial = af.factorial(af.Array([0, 1, 2])) + expected_factorial = [1, 1, 2] # Using small numbers to avoid large outputs + assert res_factorial.to_list() == expected_factorial + + def test_tgamma(self) -> None: + # Tgamma + array = af.Array([0.5, 1, 5]) # Including 0.5 to test the gamma function for non-integer + res_tgamma = af.tgamma(array) + expected_tgamma = [math.gamma(x) for x in [0.5, 1, 5]] + assert round_to(res_tgamma.to_list()) == round_to(expected_tgamma) # type: ignore[arg-type] + + def test_lgamma(self) -> None: + # Lgamma + array = af.Array([0.5, 1, 5]) + res_lgamma = af.lgamma(array) + expected_lgamma = [math.lgamma(x) for x in [0.5, 1, 5]] + assert round_to(res_lgamma.to_list()) == round_to(expected_lgamma) # type: ignore[arg-type] + + def test_logical_and(self) -> None: + # Logical And + res_and = af.logical_and(self.array1, af.Array([0, 1, 1])) + expected_and = [0 & 1, 1 & 1, 1 & 1] + assert res_and.to_list() == expected_and + + def test_logical_or(self) -> None: + # Logical OR array with array + res_or = af.logical_or(self.array1, af.Array([0, 0, 1])) + expected_or = [(x != 0) or (y != 0) for x, y in zip([1, 2, 3], [0, 0, 1])] + assert res_or.to_list() == expected_or + + # Logical OR array with scalar + res_scalar_or = af.logical_or(self.array1, 0) + expected_scalar_or = [(x != 0) or (0 != 0) for x in [1, 2, 3]] + assert res_scalar_or.to_list() == expected_scalar_or + + def test_logical_not(self) -> None: + # Logical NOT + res_not = af.logical_not(self.array1) + expected_not = [not (x != 0) for x in [1, 2, 3]] + assert res_not.to_list() == expected_not + + def test_negation(self) -> None: + res_neg = af.neg(self.array1) + expected_neg = [-1, -2, -3] + assert res_neg.to_list() == expected_neg diff --git a/tests/test_library/test_operators.py b/tests/test_library/test_operators.py deleted file mode 100644 index 41f2ec9..0000000 --- a/tests/test_library/test_operators.py +++ /dev/null @@ -1,28 +0,0 @@ -import arrayfire as af -from tests._helpers import round_to - - -class TestArithmeticOperators: - def setup_method(self) -> None: - self.array1 = af.Array([1, 2, 3]) - self.array2 = af.Array([4, 5, 6]) - - def test_add(self) -> None: - res = af.add(self.array1, self.array2) - res_sum = self.array1 + self.array2 - assert res.to_list() == res_sum.to_list() == [5, 7, 9] - - def test_sub(self) -> None: - res = af.sub(self.array1, self.array2) - res_sum = self.array1 - self.array2 - assert res.to_list() == res_sum.to_list() == [-3, -3, -3] - - def test_mul(self) -> None: - res = af.mul(self.array1, self.array2) - res_product = self.array1 * self.array2 - assert res.to_list() == res_product.to_list() == [4, 10, 18] - - def test_div(self) -> None: - res = af.div(self.array1, self.array2) - res_quotient = self.array1 / self.array2 - assert round_to(res.to_list()) == round_to(res_quotient.to_list()) == [0.25, 0.4, 0.5] From 7fe4316240886e2ec1c0335ebc203fafdd5d2521 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 5 Feb 2024 23:40:49 +0200 Subject: [PATCH 16/16] Minor fixes --- arrayfire/library/array_functions.py | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arrayfire/library/array_functions.py b/arrayfire/library/array_functions.py index c654598..2f80510 100644 --- a/arrayfire/library/array_functions.py +++ b/arrayfire/library/array_functions.py @@ -380,7 +380,7 @@ def shift(array: Array, shape: tuple[int, ...], /) -> Array: @afarray_as_array -def tile(array: Array, shape: tuple[int, ...], /) -> Array: +def tile(array: Array, /, shape: tuple[int, ...]) -> Array: if len(shape) > 4: raise ValueError("Max 4-dimensional arrays are supported.") diff --git a/requirements.txt b/requirements.txt index 06ce0a3..a1bb95b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -arrayfire-python-wrapper~=0.4.0 +arrayfire-python-wrapper==0.5.0+af3.9.0