From 75c1d43f898a835e16a9b35c608460e4c8b7b34e Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 24 Jan 2023 02:35:36 +0200 Subject: [PATCH 01/36] Refactoring of basic functionality to create an empty Array --- .github/workflows/build.yaml | 23 ++ .gitignore | 9 +- arrayfire/array.py | 67 +++-- arrayfire/array_api/__init__.py | 10 + arrayfire/array_api/_array_object.py | 314 ++++++++++++++++++++++++ arrayfire/array_api/_dtypes.py | 36 +++ arrayfire/array_api/_utils.py | 27 ++ arrayfire/array_api/config.py | 6 + arrayfire/array_api/tests/__init__.py | 0 arrayfire/array_api/tests/test_array.py | 11 + arrayfire/library.py | 5 - requirements.txt | 5 + setup.cfg | 34 ++- 13 files changed, 511 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/build.yaml create mode 100644 arrayfire/array_api/__init__.py create mode 100644 arrayfire/array_api/_array_object.py create mode 100644 arrayfire/array_api/_dtypes.py create mode 100644 arrayfire/array_api/_utils.py create mode 100644 arrayfire/array_api/config.py create mode 100644 arrayfire/array_api/tests/__init__.py create mode 100644 arrayfire/array_api/tests/test_array.py create mode 100644 requirements.txt diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 000000000..0c0b1e232 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,23 @@ +name: Run Tests + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Set up Python 3.9 + uses: actions/setup-python@v1 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Test with pytest + run: | + pytest diff --git a/.gitignore b/.gitignore index aa7bb5f1d..047939aae 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ htmlcov/ nosetests.xml coverage.xml *,cover +.pytest_cache # Translations *.mo @@ -56,6 +57,8 @@ docs/_build/ # PyBuilder target/ -# IDE -.idea -.vscode +# mypy +.mypy_cache + +# Virtual environment +venv diff --git a/arrayfire/array.py b/arrayfire/array.py index 1b71db2c7..bf71ac227 100644 --- a/arrayfire/array.py +++ b/arrayfire/array.py @@ -11,6 +11,8 @@ Array class and helper functions. """ +from .algorithm import sum, count +from .arith import cast import inspect import os from .library import * @@ -25,6 +27,7 @@ _display_dims_limit = None + def set_display_dims_limit(*dims): """ Sets the dimension limit after which array's data won't get @@ -44,6 +47,7 @@ def set_display_dims_limit(*dims): global _display_dims_limit _display_dims_limit = dims + def get_display_dims_limit(): """ Gets the dimension limit after which array's data won't get @@ -67,6 +71,7 @@ def get_display_dims_limit(): """ return _display_dims_limit + def _in_display_dims_limit(dims): if _is_running_in_py_charm: return False @@ -80,6 +85,7 @@ def _in_display_dims_limit(dims): return False return True + def _create_array(buf, numdims, idims, dtype, is_device): out_arr = c_void_ptr_t(0) c_dims = dim4(idims[0], idims[1], idims[2], idims[3]) @@ -91,6 +97,7 @@ def _create_array(buf, numdims, idims, dtype, is_device): numdims, c_pointer(c_dims), dtype.value)) return out_arr + def _create_strided_array(buf, numdims, idims, dtype, is_device, offset, strides): out_arr = c_void_ptr_t(0) c_dims = dim4(idims[0], idims[1], idims[2], idims[3]) @@ -112,16 +119,15 @@ def _create_strided_array(buf, numdims, idims, dtype, is_device, offset, strides location.value)) return out_arr + def _create_empty_array(numdims, idims, dtype): out_arr = c_void_ptr_t(0) - - if numdims == 0: return out_arr - c_dims = dim4(idims[0], idims[1], idims[2], idims[3]) safe_call(backend.get().af_create_handle(c_pointer(out_arr), numdims, c_pointer(c_dims), dtype.value)) return out_arr + def constant_array(val, d0, d1=None, d2=None, d3=None, dtype=Dtype.f32): """ Internal function to create a C array. Should not be used externall. @@ -176,6 +182,7 @@ def _binary_func(lhs, rhs, c_func): return out + def _binary_funcr(lhs, rhs, c_func): out = Array() other = lhs @@ -192,9 +199,10 @@ def _binary_funcr(lhs, rhs, c_func): return out + def _ctype_to_lists(ctype_arr, dim, shape, offset=0): if (dim == 0): - return list(ctype_arr[offset : offset + shape[0]]) + return list(ctype_arr[offset: offset + shape[0]]) else: dim_len = shape[dim] res = [[]] * dim_len @@ -203,6 +211,7 @@ def _ctype_to_lists(ctype_arr, dim, shape, offset=0): offset += shape[0] return res + def _slice_to_length(key, dim): tkey = [key.start, key.stop, key.step] @@ -221,6 +230,7 @@ def _slice_to_length(key, dim): return int(((tkey[1] - tkey[0] - 1) / tkey[2]) + 1) + def _get_info(dims, buf_len): elements = 1 numdims = 0 @@ -250,6 +260,7 @@ def _get_indices(key): return inds + def _get_assign_dims(key, idims): dims = [1]*4 @@ -296,6 +307,7 @@ def _get_assign_dims(key, idims): else: raise IndexError("Invalid type while assigning to arrayfire.array") + def transpose(a, conj=False): """ Perform the transpose on an input. @@ -318,6 +330,7 @@ def transpose(a, conj=False): safe_call(backend.get().af_transpose(c_pointer(out.arr), a.arr, conj)) return out + def transpose_inplace(a, conj=False): """ Perform inplace transpose on an input. @@ -338,6 +351,7 @@ def transpose_inplace(a, conj=False): """ safe_call(backend.get().af_transpose_inplace(a.arr, conj)) + class Array(BaseArray): """ @@ -447,8 +461,8 @@ def __init__(self, src=None, dims=None, dtype=None, is_device=False, offset=None super(Array, self).__init__() - buf=None - buf_len=0 + buf = None + buf_len = 0 if dtype is not None: if isinstance(dtype, str): @@ -458,7 +472,7 @@ def __init__(self, src=None, dims=None, dtype=None, is_device=False, offset=None else: type_char = None - _type_char='f' + _type_char = 'f' if src is not None: @@ -469,12 +483,12 @@ def __init__(self, src=None, dims=None, dtype=None, is_device=False, offset=None host = __import__("array") if isinstance(src, host.array): - buf,buf_len = src.buffer_info() + buf, buf_len = src.buffer_info() _type_char = src.typecode numdims, idims = _get_info(dims, buf_len) elif isinstance(src, list): tmp = host.array('f', src) - buf,buf_len = tmp.buffer_info() + buf, buf_len = tmp.buffer_info() _type_char = tmp.typecode numdims, idims = _get_info(dims, buf_len) elif isinstance(src, int) or isinstance(src, c_void_ptr_t): @@ -498,7 +512,7 @@ def __init__(self, src=None, dims=None, dtype=None, is_device=False, offset=None raise TypeError("src is an object of unsupported class") if (type_char is not None and - type_char != _type_char): + type_char != _type_char): raise TypeError("Can not create array of requested type from input data type") if(offset is None and strides is None): self.arr = _create_array(buf, numdims, idims, to_dtype[_type_char], is_device) @@ -620,8 +634,8 @@ def strides(self): s2 = c_dim_t(0) s3 = c_dim_t(0) safe_call(backend.get().af_get_strides(c_pointer(s0), c_pointer(s1), - c_pointer(s2), c_pointer(s3), self.arr)) - strides = (s0.value,s1.value,s2.value,s3.value) + c_pointer(s2), c_pointer(s3), self.arr)) + strides = (s0.value, s1.value, s2.value, s3.value) return strides[:self.numdims()] def elements(self): @@ -680,8 +694,8 @@ def dims(self): d2 = c_dim_t(0) d3 = c_dim_t(0) safe_call(backend.get().af_get_dims(c_pointer(d0), c_pointer(d1), - c_pointer(d2), c_pointer(d3), self.arr)) - dims = (d0.value,d1.value,d2.value,d3.value) + c_pointer(d2), c_pointer(d3), self.arr)) + dims = (d0.value, d1.value, d2.value, d3.value) return dims[:self.numdims()] @property @@ -906,7 +920,7 @@ def __itruediv__(self, other): """ Perform self /= other. """ - self = _binary_func(self, other, backend.get().af_div) + self = _binary_func(self, other, backend.get().af_div) return self def __rtruediv__(self, other): @@ -925,7 +939,7 @@ def __idiv__(self, other): """ Perform other / self. """ - self = _binary_func(self, other, backend.get().af_div) + self = _binary_func(self, other, backend.get().af_div) return self def __rdiv__(self, other): @@ -944,7 +958,7 @@ def __imod__(self, other): """ Perform self %= other. """ - self = _binary_func(self, other, backend.get().af_mod) + self = _binary_func(self, other, backend.get().af_mod) return self def __rmod__(self, other): @@ -963,7 +977,7 @@ def __ipow__(self, other): """ Perform self **= other. """ - self = _binary_func(self, other, backend.get().af_pow) + self = _binary_func(self, other, backend.get().af_pow) return self def __rpow__(self, other): @@ -1106,7 +1120,7 @@ def logical_and(self, other): Return self && other. """ out = Array() - safe_call(backend.get().af_and(c_pointer(out.arr), self.arr, other.arr)) #TODO: bcast var? + safe_call(backend.get().af_and(c_pointer(out.arr), self.arr, other.arr)) # TODO: bcast var? return out def logical_or(self, other): @@ -1114,7 +1128,7 @@ def logical_or(self, other): Return self || other. """ out = Array() - safe_call(backend.get().af_or(c_pointer(out.arr), self.arr, other.arr)) #TODO: bcast var? + safe_call(backend.get().af_or(c_pointer(out.arr), self.arr, other.arr)) # TODO: bcast var? return out def __nonzero__(self): @@ -1144,12 +1158,11 @@ def __getitem__(self, key): inds = _get_indices(key) safe_call(backend.get().af_index_gen(c_pointer(out.arr), - self.arr, c_dim_t(n_dims), inds.pointer)) + self.arr, c_dim_t(n_dims), inds.pointer)) return out except RuntimeError as e: raise IndexError(str(e)) - def __setitem__(self, key, val): """ Perform self[key] = val @@ -1175,14 +1188,14 @@ def __setitem__(self, key, val): n_dims = 1 other_arr = constant_array(val, int(num), dtype=self.type()) else: - other_arr = constant_array(val, tdims[0] , tdims[1], tdims[2], tdims[3], self.type()) + other_arr = constant_array(val, tdims[0], tdims[1], tdims[2], tdims[3], self.type()) del_other = True else: other_arr = val.arr del_other = False out_arr = c_void_ptr_t(0) - inds = _get_indices(key) + inds = _get_indices(key) safe_call(backend.get().af_assign_gen(c_pointer(out_arr), self.arr, c_dim_t(n_dims), inds.pointer, @@ -1401,6 +1414,7 @@ def to_ndarray(self, output=None): safe_call(backend.get().af_get_data_ptr(c_void_ptr_t(output.ctypes.data), tmp.arr)) return output + def display(a, precision=4): """ Displays the contents of an array. @@ -1426,6 +1440,7 @@ def display(a, precision=4): safe_call(backend.get().af_print_array_gen(name.encode('utf-8'), a.arr, c_int_t(precision))) + def save_array(key, a, filename, append=False): """ Save an array to disk. @@ -1457,6 +1472,7 @@ def save_array(key, a, filename, append=False): append)) return index.value + def read_array(filename, index=None, key=None): """ Read an array from disk. @@ -1490,6 +1506,3 @@ def read_array(filename, index=None, key=None): key.encode('utf-8'))) return out - -from .algorithm import (sum, count) -from .arith import cast diff --git a/arrayfire/array_api/__init__.py b/arrayfire/array_api/__init__.py new file mode 100644 index 000000000..2eb8c5d8c --- /dev/null +++ b/arrayfire/array_api/__init__.py @@ -0,0 +1,10 @@ +__all__ = [ + # array objects + "Array", + # dtypes + "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float32", "float64", + "complex64", "complex128", "bool"] + +from ._array_object import Array +from ._dtypes import ( + bool, complex64, complex128, float32, float64, int16, int32, int64, uint8, uint16, uint32, uint64) diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py new file mode 100644 index 000000000..6930ec9f1 --- /dev/null +++ b/arrayfire/array_api/_array_object.py @@ -0,0 +1,314 @@ +from __future__ import annotations + +import array as py_array +import ctypes +import math +from dataclasses import dataclass + +from arrayfire import backend, safe_call # TODO refactoring +from arrayfire.array import _in_display_dims_limit # TODO refactoring + +from ._dtypes import Dtype, c_dim_t, float32, supported_dtypes +from ._utils import Device, PointerSource, to_str + +ShapeType = tuple[None | int, ...] + + +@dataclass +class _ArrayBuffer: + address: int | None = None + length: int = 0 + + +class Array: + # Numpy checks this attribute to know which class handles binary builtin operations, such as __add__. + # Setting to such a high value should make sure that arrayfire has priority over + # other classes, ensuring that e.g. numpy.float32(1)*arrayfire.randu(3) is handled by + # arrayfire's __radd__() instead of numpy's __add__() + __array_priority__ = 30 + + # Initialisation + _array_buffer = _ArrayBuffer() + arr = ctypes.c_void_p(0) + + def __init__( + self, x: None | Array | py_array.array | list = None, dtype: None | Dtype = None, + pointer_source: PointerSource = PointerSource.host, shape: None | tuple[int] = None, + offset: None | ctypes._SimpleCData[int] = None, strides: None | tuple[int, ...] = None) -> None: + + if isinstance(dtype, str): + dtype = _str_to_dtype(dtype) + + if dtype is None: + _no_initial_dtype = True + dtype = float32 + + if x is None: + if not shape: # shape is None or empty tuple + safe_call(backend.get().af_create_handle( + ctypes.pointer(self.arr), 0, ctypes.pointer(dim4()), dtype.c_api_value)) + return + + # NOTE: applies inplace changes for self.arr + safe_call(backend.get().af_create_handle( + ctypes.pointer(self.arr), len(shape), ctypes.pointer(dim4(*shape)), dtype.c_api_value)) + return + + if isinstance(x, Array): + safe_call(backend.get().af_retain_array(ctypes.pointer(self.arr), x.arr)) + return + + if isinstance(x, py_array.array): + _type_char = x.typecode + _array_buffer = _ArrayBuffer(*x.buffer_info()) + numdims, idims = _get_info(shape, _array_buffer.length) + + elif isinstance(x, list): + _array = py_array.array("f", x) # BUG [True, False] -> dtype: f32 # TODO add int and float + _type_char = _array.typecode + _array_buffer = _ArrayBuffer(*_array.buffer_info()) + numdims, idims = _get_info(shape, _array_buffer.length) + + elif isinstance(x, int) or isinstance(x, ctypes.c_void_p): # TODO + _array_buffer = _ArrayBuffer(x if not isinstance(x, ctypes.c_void_p) else x.value) + numdims, idims = _get_info(shape, _array_buffer.length) + + if not math.prod(idims): + raise RuntimeError("Expected to receive the initial shape due to the x being a data pointer.") + + if _no_initial_dtype: + raise TypeError("Expected to receive the initial dtype due to the x being a data pointer.") + + _type_char = dtype.typecode + + else: + raise TypeError("Passed object x is an object of unsupported class.") + + if not _no_initial_dtype and dtype.typecode != _type_char: + raise TypeError("Can not create array of requested type from input data type") + + if not (offset or strides): + if pointer_source == PointerSource.host: + safe_call(backend.get().af_create_array( + ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), numdims, + ctypes.pointer(dim4(*idims)), dtype.c_api_value)) + return + + safe_call(backend.get().af_device_array( + ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), numdims, + ctypes.pointer(dim4(*idims)), dtype.c_api_value)) + return + + if offset is None: # TODO + offset = c_dim_t(0) + + if strides is None: # TODO + strides = (1, idims[0], idims[0]*idims[1], idims[0]*idims[1]*idims[2]) + + if len(strides) < 4: + strides += (strides[-1], ) * (4 - len(strides)) + strides_dim4 = dim4(*strides) + + safe_call(backend.get().af_create_strided_array( + ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), offset, numdims, + ctypes.pointer(dim4(*idims)), ctypes.pointer(strides_dim4), dtype.c_api_value, pointer_source.value)) + + def __str__(self) -> str: # FIXME + if not _in_display_dims_limit(self.shape): + return _metadata_string(self.dtype, self.shape) + + return _metadata_string(self.dtype) + self._as_str() + + def __repr__(self) -> str: # FIXME + return _metadata_string(self.dtype, self.shape) + + def __len__(self) -> int: + return self.shape[0] if self.shape else 0 # type: ignore[return-value] + + def __pos__(self) -> Array: + """y + Return +self + """ + return self + + def __neg__(self) -> Array: + """ + Return -self + """ + # return 0 - self + raise NotImplementedError + + def __add__(self, other: int | float | Array, /) -> Array: + """ + Return self + other. + """ + # return _binary_func(self, other, backend.get().af_add) # TODO + raise NotImplementedError + + @property + def dtype(self) -> Dtype: + out = ctypes.c_int() + safe_call(backend.get().af_get_type(ctypes.pointer(out), self.arr)) + return _c_api_value_to_dtype(out.value) + + @property + def device(self) -> Device: + raise NotImplementedError + + @property + def mT(self) -> Array: + # TODO + raise NotImplementedError + + @property + def T(self) -> Array: + # TODO + raise NotImplementedError + + @property + def size(self) -> None | int: + # NOTE previously - elements() + out = c_dim_t(0) + safe_call(backend.get().af_get_elements(ctypes.pointer(out), self.arr)) + return out.value + + @property + def ndim(self) -> int: + nd = ctypes.c_uint(0) + safe_call(backend.get().af_get_numdims(ctypes.pointer(nd), self.arr)) + return nd.value + + @property + def shape(self) -> ShapeType: + """ + Return the shape of the array as a tuple. + """ + # TODO refactor + d0 = c_dim_t(0) + d1 = c_dim_t(0) + d2 = c_dim_t(0) + d3 = c_dim_t(0) + safe_call(backend.get().af_get_dims( + ctypes.pointer(d0), ctypes.pointer(d1), ctypes.pointer(d2), ctypes.pointer(d3), self.arr)) + dims = (d0.value, d1.value, d2.value, d3.value) + return dims[:self.ndim] # FIXME An array dimension must be None if and only if a dimension is unknown + + def _as_str(self) -> str: + arr_str = ctypes.c_char_p(0) + # FIXME add description to passed arguments + safe_call(backend.get().af_array_to_string(ctypes.pointer(arr_str), "", self.arr, 4, True)) + py_str = to_str(arr_str) + safe_call(backend.get().af_free_host(arr_str)) + return py_str + + # def _get_metadata_str(self, show_dims: bool = True) -> str: + # return ( + # "arrayfire.Array()\n" + # f"Type: {self.dtype.typename}\n" + # f"Dims: {str(self._dims) if show_dims else ''}") + + # @property + # def dtype(self) -> ...: + # dty = ctypes.c_int() + # safe_call(backend.get().af_get_type(ctypes.pointer(dty), self.arr)) # -> new dty + +# @safe_call +# def backend() +# ... + +# @backend(safe=True) +# def af_get_type(arr) -> ...: +# dty = ctypes.c_int() +# safe_call(backend.get().af_get_type(ctypes.pointer(dty), self.arr)) # -> new dty +# return dty + +# def new_dtype(): +# return af_get_type(self.arr) + + +def _metadata_string(dtype: Dtype, dims: None | ShapeType = None) -> str: + return ( + "arrayfire.Array()\n" + f"Type: {dtype.typename}\n" + f"Dims: {str(dims) if dims else ''}") + + +def _get_info(shape: None | tuple[int], buffer_length: int) -> tuple[int, list[int]]: + # TODO refactor + if shape: + numdims = len(shape) + idims = [1]*4 + for i in range(numdims): + idims[i] = shape[i] + elif (buffer_length != 0): + idims = [buffer_length, 1, 1, 1] + numdims = 1 + else: + raise RuntimeError("Invalid size") + + return numdims, idims + + +def _c_api_value_to_dtype(value: int) -> Dtype: + for dtype in supported_dtypes: + if value == dtype.c_api_value: + return dtype + + raise TypeError("There is no supported dtype that matches passed dtype C API value.") + + +def _str_to_dtype(value: int) -> Dtype: + for dtype in supported_dtypes: + if value == dtype.typecode or value == dtype.typename: + return dtype + + raise TypeError("There is no supported dtype that matches passed dtype typecode.") + +# TODO +# def _binary_func(lhs: int | float | Array, rhs: int | float | Array, c_func: Any) -> Array: # TODO replace Any +# out = Array() +# other = rhs + +# if is_number(rhs): +# ldims = _fill_dim4_tuple(lhs.shape) +# rty = implicit_dtype(rhs, lhs.type()) +# other = Array() +# other.arr = constant_array(rhs, ldims[0], ldims[1], ldims[2], ldims[3], rty.value) +# elif not isinstance(rhs, Array): +# raise TypeError("Invalid parameter to binary function") + +# safe_call(c_func(c_pointer(out.arr), lhs.arr, other.arr, _bcast_var.get())) + +# return out + + +def dim4(d0: int = 1, d1: int = 1, d2: int = 1, d3: int = 1): # type: ignore # FIXME + c_dim4 = c_dim_t * 4 # ctypes.c_int | ctypes.c_longlong * 4 + out = c_dim4(1, 1, 1, 1) + + for i, dim in enumerate((d0, d1, d2, d3)): + if dim is not None: + out[i] = c_dim_t(dim) + + return out + +# TODO replace candidate below +# def dim4_to_tuple(shape: ShapeType, default: int=1) -> ShapeType: +# assert(isinstance(dims, tuple)) + +# if (default is not None): +# assert(is_number(default)) + +# out = [default]*4 + +# for i, dim in enumerate(dims): +# out[i] = dim + +# return tuple(out) + +# def _fill_dim4_tuple(shape: ShapeType) -> tuple[int, ...]: +# out = tuple([1 if value is None else value for value in shape]) +# if len(out) == 4: +# return out + +# return out + (1,)*(4-len(out)) diff --git a/arrayfire/array_api/_dtypes.py b/arrayfire/array_api/_dtypes.py new file mode 100644 index 000000000..dc7ef71fd --- /dev/null +++ b/arrayfire/array_api/_dtypes.py @@ -0,0 +1,36 @@ +import ctypes +from dataclasses import dataclass +from typing import Type + +from .config import is_arch_x86 + +c_dim_t = ctypes.c_int if is_arch_x86() else ctypes.c_longlong + + +@dataclass +class Dtype: + typecode: str + c_type: Type[ctypes._SimpleCData] + typename: str + c_api_value: int # Internal use only + + +# Specification required +# int8 - Not Supported, b8? # HACK Dtype("i8", ctypes.c_char, "int8", 4) +int16 = Dtype("h", ctypes.c_short, "short int", 10) +int32 = Dtype("i", ctypes.c_int, "int", 5) +int64 = Dtype("l", ctypes.c_longlong, "long int", 8) +uint8 = Dtype("B", ctypes.c_ubyte, "unsigned_char", 7) +uint16 = Dtype("H", ctypes.c_ushort, "unsigned short int", 11) +uint32 = Dtype("I", ctypes.c_uint, "unsigned int", 6) +uint64 = Dtype("L", ctypes.c_ulonglong, "unsigned long int", 9) +float32 = Dtype("f", ctypes.c_float, "float", 0) +float64 = Dtype("d", ctypes.c_double, "double", 2) +complex64 = Dtype("F", ctypes.c_float*2, "float complext", 1) # type: ignore[arg-type] +complex128 = Dtype("D", ctypes.c_double*2, "double complext", 3) # type: ignore[arg-type] +bool = Dtype("b", ctypes.c_bool, "bool", 4) + +supported_dtypes = [ + # int8, + int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128, bool +] diff --git a/arrayfire/array_api/_utils.py b/arrayfire/array_api/_utils.py new file mode 100644 index 000000000..e235480c2 --- /dev/null +++ b/arrayfire/array_api/_utils.py @@ -0,0 +1,27 @@ +import ctypes +import enum +import numbers +from typing import Any + + +class Device(enum.Enum): + # HACK. TODO make it real + cpu = "cpu" + gpu = "gpu" + + +class PointerSource(enum.Enum): + """ + Source of the pointer + """ + # FIXME + device = 0 + host = 1 + + +def to_str(c_str: ctypes.c_char_p) -> str: + return str(c_str.value.decode("utf-8")) # type: ignore[union-attr] + + +def is_number(number: Any) -> bool: + return isinstance(number, numbers.Number) diff --git a/arrayfire/array_api/config.py b/arrayfire/array_api/config.py new file mode 100644 index 000000000..588cbdfd2 --- /dev/null +++ b/arrayfire/array_api/config.py @@ -0,0 +1,6 @@ +import platform + + +def is_arch_x86() -> bool: + machine = platform.machine() + return platform.architecture()[0][0:2] == "32" and (machine[-2:] == "86" or machine[0:3] == "arm") diff --git a/arrayfire/array_api/tests/__init__.py b/arrayfire/array_api/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/arrayfire/array_api/tests/test_array.py b/arrayfire/array_api/tests/test_array.py new file mode 100644 index 000000000..066167ced --- /dev/null +++ b/arrayfire/array_api/tests/test_array.py @@ -0,0 +1,11 @@ +from arrayfire.array_api import Array, float32 + + +def test_empty_array() -> None: + array = Array() + + assert array.dtype == float32 + assert array.ndim == 0 + assert array.size == 0 + assert array.shape == () + assert len(array) == 0 diff --git a/arrayfire/library.py b/arrayfire/library.py index 1b3c8b3ea..df68f97d8 100644 --- a/arrayfire/library.py +++ b/arrayfire/library.py @@ -506,7 +506,6 @@ def _setup(): AF_PATH = os.environ['AF_PATH'] except KeyError: AF_PATH = None - pass AF_SEARCH_PATH = AF_PATH @@ -514,7 +513,6 @@ def _setup(): CUDA_PATH = os.environ['CUDA_PATH'] except KeyError: CUDA_PATH= None - pass CUDA_FOUND = False @@ -666,7 +664,6 @@ def __init__(self): VERBOSE_LOADS = os.environ['AF_VERBOSE_LOADS'] == '1' except KeyError: VERBOSE_LOADS = False - pass for libname in libnames: try: @@ -679,7 +676,6 @@ def __init__(self): if VERBOSE_LOADS: traceback.print_exc() print('Unable to load ' + full_libname) - pass c_dim4 = c_dim_t*4 out = c_void_ptr_t(0) @@ -720,7 +716,6 @@ def __init__(self): if VERBOSE_LOADS: traceback.print_exc() print('Unable to load ' + full_libname) - pass if (self.__name is None): raise RuntimeError("Could not load any ArrayFire libraries.\n" + more_info_str) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..3b997e873 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# Build requirements +wheel~=0.38.4 + +# Development requirements +-e .[dev,test] diff --git a/setup.cfg b/setup.cfg index e5414158b..84f512c2e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,16 +16,48 @@ classifiers = [options] packages = find: -install_requires= +install_requires = scikit-build +python_requires = + >=3.8.0 [options.packages.find] include = arrayfire exclude = examples tests +install_requires = + numpy~=1.23.4 + +[options.extras_require] +dev = + autopep8~=1.6.0 + isort~=5.10.1 + flake8~=4.0.1 + flake8-quotes~=3.2.0 + mypy~=0.942 +test = + pytest~=7.1.2 + pytest-cov~=3.0.0 + pytest-isort~=3.0.0 + pytest-flake8~=1.1.1 + pytest-mypy~=0.9.1 + +[tool:isort] +line_length = 119 +multi_line_output = 4 [flake8] +exclude = venv application-import-names = arrayfire import-order-style = pep8 +inline-quotes = double max-line-length = 119 + +[mypy] +exclude = venv +disallow_incomplete_defs = true +disallow_untyped_defs = true +ignore_missing_imports = true +show_error_codes = true +warn_return_any = true From b14aa9124afb0978c561ea78db9da9dc1546c5ab Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 24 Jan 2023 19:09:59 +0200 Subject: [PATCH 02/36] Replace dim4 with CShape --- arrayfire/array_api/__init__.py | 3 +- arrayfire/array_api/_array_object.py | 93 +++++++------------------ arrayfire/array_api/_dtypes.py | 37 +++++++++- arrayfire/array_api/pytest.ini | 4 ++ arrayfire/array_api/tests/test_array.py | 17 +++++ 5 files changed, 83 insertions(+), 71 deletions(-) create mode 100644 arrayfire/array_api/pytest.ini diff --git a/arrayfire/array_api/__init__.py b/arrayfire/array_api/__init__.py index 2eb8c5d8c..2de1832ab 100644 --- a/arrayfire/array_api/__init__.py +++ b/arrayfire/array_api/__init__.py @@ -6,5 +6,4 @@ "complex64", "complex128", "bool"] from ._array_object import Array -from ._dtypes import ( - bool, complex64, complex128, float32, float64, int16, int32, int64, uint8, uint16, uint32, uint64) +from ._dtypes import bool, complex64, complex128, float32, float64, int16, int32, int64, uint8, uint16, uint32, uint64 diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index 6930ec9f1..f271b63eb 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -2,13 +2,12 @@ import array as py_array import ctypes -import math from dataclasses import dataclass from arrayfire import backend, safe_call # TODO refactoring from arrayfire.array import _in_display_dims_limit # TODO refactoring -from ._dtypes import Dtype, c_dim_t, float32, supported_dtypes +from ._dtypes import CShape, Dtype, c_dim_t, float32, supported_dtypes from ._utils import Device, PointerSource, to_str ShapeType = tuple[None | int, ...] @@ -28,7 +27,6 @@ class Array: __array_priority__ = 30 # Initialisation - _array_buffer = _ArrayBuffer() arr = ctypes.c_void_p(0) def __init__( @@ -46,12 +44,12 @@ def __init__( if x is None: if not shape: # shape is None or empty tuple safe_call(backend.get().af_create_handle( - ctypes.pointer(self.arr), 0, ctypes.pointer(dim4()), dtype.c_api_value)) + ctypes.pointer(self.arr), 0, ctypes.pointer(CShape().c_array), dtype.c_api_value)) return # NOTE: applies inplace changes for self.arr safe_call(backend.get().af_create_handle( - ctypes.pointer(self.arr), len(shape), ctypes.pointer(dim4(*shape)), dtype.c_api_value)) + ctypes.pointer(self.arr), len(shape), ctypes.pointer(CShape(*shape).c_array), dtype.c_api_value)) return if isinstance(x, Array): @@ -61,19 +59,16 @@ def __init__( if isinstance(x, py_array.array): _type_char = x.typecode _array_buffer = _ArrayBuffer(*x.buffer_info()) - numdims, idims = _get_info(shape, _array_buffer.length) elif isinstance(x, list): _array = py_array.array("f", x) # BUG [True, False] -> dtype: f32 # TODO add int and float _type_char = _array.typecode _array_buffer = _ArrayBuffer(*_array.buffer_info()) - numdims, idims = _get_info(shape, _array_buffer.length) elif isinstance(x, int) or isinstance(x, ctypes.c_void_p): # TODO _array_buffer = _ArrayBuffer(x if not isinstance(x, ctypes.c_void_p) else x.value) - numdims, idims = _get_info(shape, _array_buffer.length) - if not math.prod(idims): + if not shape: raise RuntimeError("Expected to receive the initial shape due to the x being a data pointer.") if _no_initial_dtype: @@ -84,34 +79,37 @@ def __init__( else: raise TypeError("Passed object x is an object of unsupported class.") + _cshape = _get_cshape(shape, _array_buffer.length) + if not _no_initial_dtype and dtype.typecode != _type_char: raise TypeError("Can not create array of requested type from input data type") if not (offset or strides): if pointer_source == PointerSource.host: safe_call(backend.get().af_create_array( - ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), numdims, - ctypes.pointer(dim4(*idims)), dtype.c_api_value)) + ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), _cshape.original_shape, + ctypes.pointer(_cshape.c_array), dtype.c_api_value)) return safe_call(backend.get().af_device_array( - ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), numdims, - ctypes.pointer(dim4(*idims)), dtype.c_api_value)) + ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), _cshape.original_shape, + ctypes.pointer(_cshape.c_array), dtype.c_api_value)) return - if offset is None: # TODO + if offset is None: offset = c_dim_t(0) - if strides is None: # TODO - strides = (1, idims[0], idims[0]*idims[1], idims[0]*idims[1]*idims[2]) + if strides is None: + strides = (1, _cshape[0], _cshape[0]*_cshape[1], _cshape[0]*_cshape[1]*_cshape[2]) if len(strides) < 4: strides += (strides[-1], ) * (4 - len(strides)) - strides_dim4 = dim4(*strides) + strides_cshape = CShape(*strides).c_array safe_call(backend.get().af_create_strided_array( - ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), offset, numdims, - ctypes.pointer(dim4(*idims)), ctypes.pointer(strides_dim4), dtype.c_api_value, pointer_source.value)) + ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), offset, _cshape.original_shape, + ctypes.pointer(_cshape.c_array), ctypes.pointer(strides_cshape), dtype.c_api_value, + pointer_source.value)) def __str__(self) -> str: # FIXME if not _in_display_dims_limit(self.shape): @@ -126,7 +124,7 @@ def __len__(self) -> int: return self.shape[0] if self.shape else 0 # type: ignore[return-value] def __pos__(self) -> Array: - """y + """ Return +self """ return self @@ -190,8 +188,7 @@ def shape(self) -> ShapeType: d3 = c_dim_t(0) safe_call(backend.get().af_get_dims( ctypes.pointer(d0), ctypes.pointer(d1), ctypes.pointer(d2), ctypes.pointer(d3), self.arr)) - dims = (d0.value, d1.value, d2.value, d3.value) - return dims[:self.ndim] # FIXME An array dimension must be None if and only if a dimension is unknown + return (d0.value, d1.value, d2.value, d3.value)[:self.ndim] # Skip passing None values def _as_str(self) -> str: arr_str = ctypes.c_char_p(0) @@ -201,30 +198,6 @@ def _as_str(self) -> str: safe_call(backend.get().af_free_host(arr_str)) return py_str - # def _get_metadata_str(self, show_dims: bool = True) -> str: - # return ( - # "arrayfire.Array()\n" - # f"Type: {self.dtype.typename}\n" - # f"Dims: {str(self._dims) if show_dims else ''}") - - # @property - # def dtype(self) -> ...: - # dty = ctypes.c_int() - # safe_call(backend.get().af_get_type(ctypes.pointer(dty), self.arr)) # -> new dty - -# @safe_call -# def backend() -# ... - -# @backend(safe=True) -# def af_get_type(arr) -> ...: -# dty = ctypes.c_int() -# safe_call(backend.get().af_get_type(ctypes.pointer(dty), self.arr)) # -> new dty -# return dty - -# def new_dtype(): -# return af_get_type(self.arr) - def _metadata_string(dtype: Dtype, dims: None | ShapeType = None) -> str: return ( @@ -233,20 +206,14 @@ def _metadata_string(dtype: Dtype, dims: None | ShapeType = None) -> str: f"Dims: {str(dims) if dims else ''}") -def _get_info(shape: None | tuple[int], buffer_length: int) -> tuple[int, list[int]]: - # TODO refactor +def _get_cshape(shape: None | tuple[int], buffer_length: int) -> CShape: if shape: - numdims = len(shape) - idims = [1]*4 - for i in range(numdims): - idims[i] = shape[i] - elif (buffer_length != 0): - idims = [buffer_length, 1, 1, 1] - numdims = 1 - else: - raise RuntimeError("Invalid size") + return CShape(*shape) + + if buffer_length != 0: + return CShape(buffer_length) - return numdims, idims + raise RuntimeError("Shape and buffer length are size invalid.") def _c_api_value_to_dtype(value: int) -> Dtype: @@ -282,16 +249,6 @@ def _str_to_dtype(value: int) -> Dtype: # return out -def dim4(d0: int = 1, d1: int = 1, d2: int = 1, d3: int = 1): # type: ignore # FIXME - c_dim4 = c_dim_t * 4 # ctypes.c_int | ctypes.c_longlong * 4 - out = c_dim4(1, 1, 1, 1) - - for i, dim in enumerate((d0, d1, d2, d3)): - if dim is not None: - out[i] = c_dim_t(dim) - - return out - # TODO replace candidate below # def dim4_to_tuple(shape: ShapeType, default: int=1) -> ShapeType: # assert(isinstance(dims, tuple)) diff --git a/arrayfire/array_api/_dtypes.py b/arrayfire/array_api/_dtypes.py index dc7ef71fd..7f099730f 100644 --- a/arrayfire/array_api/_dtypes.py +++ b/arrayfire/array_api/_dtypes.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import ctypes from dataclasses import dataclass from typing import Type @@ -31,6 +33,39 @@ class Dtype: bool = Dtype("b", ctypes.c_bool, "bool", 4) supported_dtypes = [ - # int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128, bool ] + + +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)) + + +# @safe_call +# def backend() +# ... + +# @backend(safe=True) +# def af_get_type(arr) -> ...: +# dty = ctypes.c_int() +# safe_call(backend.get().af_get_type(ctypes.pointer(dty), self.arr)) # -> new dty +# return dty + +# def new_dtype(): +# return af_get_type(self.arr) diff --git a/arrayfire/array_api/pytest.ini b/arrayfire/array_api/pytest.ini new file mode 100644 index 000000000..1337bcae1 --- /dev/null +++ b/arrayfire/array_api/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +addopts = --cache-clear --cov=./arrayfire/array_api --flake8 --mypy --isort ./arrayfire/array_api +console_output_style = classic +markers = mypy diff --git a/arrayfire/array_api/tests/test_array.py b/arrayfire/array_api/tests/test_array.py index 066167ced..0cf3c9836 100644 --- a/arrayfire/array_api/tests/test_array.py +++ b/arrayfire/array_api/tests/test_array.py @@ -1,3 +1,5 @@ +import pytest + from arrayfire.array_api import Array, float32 @@ -9,3 +11,18 @@ def test_empty_array() -> None: assert array.size == 0 assert array.shape == () assert len(array) == 0 + + +def test_array_from_1d_list() -> None: + array = Array([1, 2, 3]) + + assert array.dtype == float32 + assert array.ndim == 1 + assert array.size == 3 + assert array.shape == (3,) + assert len(array) == 3 + + +def test_array_from_2d_list() -> None: + with pytest.raises(TypeError): + Array([[1, 2, 3], [1, 2, 3]]) From eadbe9b5685f8621a0fd8ecc8bb0f4b899fb8cbb Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 24 Jan 2023 19:55:53 +0200 Subject: [PATCH 03/36] Add tests. Minor fixes. Update CI --- .github/workflows/build.yaml | 11 ++- arrayfire/array_api/_array_object.py | 13 +-- arrayfire/array_api/pytest.ini | 2 +- arrayfire/array_api/tests/test_array.py | 28 ------ .../array_api/tests/test_array_object.py | 95 +++++++++++++++++++ 5 files changed, 110 insertions(+), 39 deletions(-) delete mode 100644 arrayfire/array_api/tests/test_array.py create mode 100644 arrayfire/array_api/tests/test_array_object.py diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0c0b1e232..e86d3fc90 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -9,15 +9,18 @@ jobs: steps: - uses: actions/checkout@v1 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v1 with: - python-version: 3.9 + python-version: 3.10 - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt + - name: Test with pytest - run: | - pytest + run: pytest + + - name: Test array_api + run: python -m pytest arrayfire/array_api diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index f271b63eb..9e9c31d89 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -10,7 +10,7 @@ from ._dtypes import CShape, Dtype, c_dim_t, float32, supported_dtypes from ._utils import Device, PointerSource, to_str -ShapeType = tuple[None | int, ...] +ShapeType = tuple[int, ...] @dataclass @@ -30,9 +30,10 @@ class Array: arr = ctypes.c_void_p(0) def __init__( - self, x: None | Array | py_array.array | list = None, dtype: None | Dtype = None, - pointer_source: PointerSource = PointerSource.host, shape: None | tuple[int] = None, - offset: None | ctypes._SimpleCData[int] = None, strides: None | tuple[int, ...] = None) -> None: + self, x: None | Array | py_array.array | int | ctypes.c_void_p | list = None, dtype: None | Dtype = None, + pointer_source: PointerSource = PointerSource.host, shape: None | ShapeType = None, + offset: None | ctypes._SimpleCData[int] = None, strides: None | ShapeType = None) -> None: + _no_initial_dtype = False # HACK, FIXME if isinstance(dtype, str): dtype = _str_to_dtype(dtype) @@ -69,7 +70,7 @@ def __init__( _array_buffer = _ArrayBuffer(x if not isinstance(x, ctypes.c_void_p) else x.value) if not shape: - raise RuntimeError("Expected to receive the initial shape due to the x being a data pointer.") + raise TypeError("Expected to receive the initial shape due to the x being a data pointer.") if _no_initial_dtype: raise TypeError("Expected to receive the initial dtype due to the x being a data pointer.") @@ -206,7 +207,7 @@ def _metadata_string(dtype: Dtype, dims: None | ShapeType = None) -> str: f"Dims: {str(dims) if dims else ''}") -def _get_cshape(shape: None | tuple[int], buffer_length: int) -> CShape: +def _get_cshape(shape: None | ShapeType, buffer_length: int) -> CShape: if shape: return CShape(*shape) diff --git a/arrayfire/array_api/pytest.ini b/arrayfire/array_api/pytest.ini index 1337bcae1..43feed854 100644 --- a/arrayfire/array_api/pytest.ini +++ b/arrayfire/array_api/pytest.ini @@ -1,4 +1,4 @@ [pytest] -addopts = --cache-clear --cov=./arrayfire/array_api --flake8 --mypy --isort ./arrayfire/array_api +addopts = --cache-clear --cov=./arrayfire/array_api --flake8 --isort ./arrayfire/array_api console_output_style = classic markers = mypy diff --git a/arrayfire/array_api/tests/test_array.py b/arrayfire/array_api/tests/test_array.py deleted file mode 100644 index 0cf3c9836..000000000 --- a/arrayfire/array_api/tests/test_array.py +++ /dev/null @@ -1,28 +0,0 @@ -import pytest - -from arrayfire.array_api import Array, float32 - - -def test_empty_array() -> None: - array = Array() - - assert array.dtype == float32 - assert array.ndim == 0 - assert array.size == 0 - assert array.shape == () - assert len(array) == 0 - - -def test_array_from_1d_list() -> None: - array = Array([1, 2, 3]) - - assert array.dtype == float32 - assert array.ndim == 1 - assert array.size == 3 - assert array.shape == (3,) - assert len(array) == 3 - - -def test_array_from_2d_list() -> None: - with pytest.raises(TypeError): - Array([[1, 2, 3], [1, 2, 3]]) diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py new file mode 100644 index 000000000..e52f83556 --- /dev/null +++ b/arrayfire/array_api/tests/test_array_object.py @@ -0,0 +1,95 @@ +import pytest + +from arrayfire.array_api import Array, float32, int16 +from arrayfire.array_api._dtypes import supported_dtypes + + +def test_empty_array() -> None: + array = Array() + + assert array.dtype == float32 + assert array.ndim == 0 + assert array.size == 0 + assert array.shape == () + assert len(array) == 0 + + +def test_empty_array_with_nonempty_dtype() -> None: + array = Array(dtype=int16) + + assert array.dtype == int16 + assert array.ndim == 0 + assert array.size == 0 + assert array.shape == () + assert len(array) == 0 + + +def test_empty_array_with_nonempty_shape() -> None: + array = Array(shape=(2, 3)) + + assert array.dtype == float32 + assert array.ndim == 2 + assert array.size == 6 + assert array.shape == (2, 3) + assert len(array) == 2 + + +def test_array_from_1d_list() -> None: + array = Array([1, 2, 3]) + + assert array.dtype == float32 + assert array.ndim == 1 + assert array.size == 3 + assert array.shape == (3,) + assert len(array) == 3 + + +def test_array_from_2d_list() -> None: + with pytest.raises(TypeError): + Array([[1, 2, 3], [1, 2, 3]]) + + +def test_array_from_list_with_unsupported_dtype() -> None: + for dtype in supported_dtypes: + if dtype == float32: + continue + with pytest.raises(TypeError): + Array([1, 2, 3], dtype=dtype) + + +def test_array_from_af_array() -> None: + array1 = Array([1]) + array2 = Array(array1) + + assert array1.dtype == array2.dtype == float32 + assert array1.ndim == array2.ndim == 1 + assert array1.size == array2.size == 1 + assert array1.shape == array2.shape == (1,) + assert len(array1) == len(array2) == 1 + + +def test_array_from_int_without_shape() -> None: + with pytest.raises(TypeError): + Array(1) + + +def test_array_from_int_without_dtype() -> None: + with pytest.raises(TypeError): + Array(1, shape=(1,)) + +# def test_array_from_int_with_parameters() -> None: # BUG seg fault +# array = Array(1, shape=(1,), dtype=float32) + +# assert array.dtype == float32 +# assert array.ndim == 1 +# assert array.size == 1 +# assert array.shape == (1,) +# assert len(array) == 1 + + +def test_array_from_unsupported_type() -> None: + with pytest.raises(TypeError): + Array((5, 5)) # type: ignore[arg-type] + + with pytest.raises(TypeError): + Array({1: 2, 3: 4}) # type: ignore[arg-type] From c13a59f18ff5a90208a923738a236fc2823ef43d Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 24 Jan 2023 19:56:52 +0200 Subject: [PATCH 04/36] Fix CI --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e86d3fc90..8e420cfbc 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -12,7 +12,7 @@ jobs: - name: Set up Python 3.10 uses: actions/setup-python@v1 with: - python-version: 3.10 + python-version: "3.10" - name: Install dependencies run: | From f0f57e8c269c6efa30669162cce7ff76d3efd19e Mon Sep 17 00:00:00 2001 From: Anton Date: Thu, 26 Jan 2023 21:05:29 +0200 Subject: [PATCH 05/36] Add arithmetic operators w/o tests --- arrayfire/array_api/_array_object.py | 158 +++++++++++++++++++-------- arrayfire/array_api/_utils.py | 9 +- 2 files changed, 114 insertions(+), 53 deletions(-) diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index 9e9c31d89..a18309dc6 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -3,14 +3,25 @@ import array as py_array import ctypes from dataclasses import dataclass +from typing import Any from arrayfire import backend, safe_call # TODO refactoring from arrayfire.array import _in_display_dims_limit # TODO refactoring -from ._dtypes import CShape, Dtype, c_dim_t, float32, supported_dtypes -from ._utils import Device, PointerSource, to_str +from ._dtypes import CShape, Dtype +from ._dtypes import bool as af_bool +from ._dtypes import c_dim_t +from ._dtypes import complex64 as af_complex64 +from ._dtypes import complex128 as af_complex128 +from ._dtypes import float32 as af_float32 +from ._dtypes import float64 as af_float64 +from ._dtypes import int64 as af_int64 +from ._dtypes import supported_dtypes +from ._dtypes import uint64 as af_uint64 +from ._utils import PointerSource, is_number, to_str ShapeType = tuple[int, ...] +_bcast_var = False # HACK, TODO replace for actual bcast_var after refactoring @dataclass @@ -40,7 +51,7 @@ def __init__( if dtype is None: _no_initial_dtype = True - dtype = float32 + dtype = af_float32 if x is None: if not shape: # shape is None or empty tuple @@ -134,15 +145,47 @@ def __neg__(self) -> Array: """ Return -self """ - # return 0 - self - raise NotImplementedError + return 0 - self def __add__(self, other: int | float | Array, /) -> Array: + # TODO discuss either we need to support complex and bool as other input type """ Return self + other. """ - # return _binary_func(self, other, backend.get().af_add) # TODO - raise NotImplementedError + return _process_c_function(self, other, backend.get().af_add) + + def __sub__(self, other: int | float | bool | complex | Array, /) -> Array: + """ + Return self - other. + """ + return _process_c_function(self, other, backend.get().af_sub) + + def __mul__(self, other: int | float | bool | complex | Array, /) -> Array: + """ + Return self * other. + """ + return _process_c_function(self, other, backend.get().af_mul) + + def __truediv__(self, other: int | float | bool | complex | Array, /) -> Array: + """ + Return self / other. + """ + return _process_c_function(self, other, backend.get().af_div) + + def __floordiv__(self, other: int | float | bool | complex | Array, /) -> Array: + return NotImplemented + + def __mod__(self, other: int | float | bool | complex | Array, /) -> Array: + """ + Return self % other. + """ + return _process_c_function(self, other, backend.get().af_mod) + + def __pow__(self, other: int | float | bool | complex | Array, /) -> Array: + """ + Return self ** other. + """ + return _process_c_function(self, other, backend.get().af_pow) @property def dtype(self) -> Dtype: @@ -151,7 +194,7 @@ def dtype(self) -> Dtype: return _c_api_value_to_dtype(out.value) @property - def device(self) -> Device: + def device(self) -> Any: raise NotImplementedError @property @@ -232,41 +275,66 @@ def _str_to_dtype(value: int) -> Dtype: raise TypeError("There is no supported dtype that matches passed dtype typecode.") -# TODO -# def _binary_func(lhs: int | float | Array, rhs: int | float | Array, c_func: Any) -> Array: # TODO replace Any -# out = Array() -# other = rhs - -# if is_number(rhs): -# ldims = _fill_dim4_tuple(lhs.shape) -# rty = implicit_dtype(rhs, lhs.type()) -# other = Array() -# other.arr = constant_array(rhs, ldims[0], ldims[1], ldims[2], ldims[3], rty.value) -# elif not isinstance(rhs, Array): -# raise TypeError("Invalid parameter to binary function") - -# safe_call(c_func(c_pointer(out.arr), lhs.arr, other.arr, _bcast_var.get())) - -# return out - - -# TODO replace candidate below -# def dim4_to_tuple(shape: ShapeType, default: int=1) -> ShapeType: -# assert(isinstance(dims, tuple)) - -# if (default is not None): -# assert(is_number(default)) - -# out = [default]*4 - -# for i, dim in enumerate(dims): -# out[i] = dim - -# return tuple(out) - -# def _fill_dim4_tuple(shape: ShapeType) -> tuple[int, ...]: -# out = tuple([1 if value is None else value for value in shape]) -# if len(out) == 4: -# return out -# return out + (1,)*(4-len(out)) +def _process_c_function( + target: Array, other: int | float | bool | complex | Array, c_function: Any) -> Array: + out = Array() + + if isinstance(other, Array): + safe_call(c_function(ctypes.pointer(out.arr), target.arr, other.arr, _bcast_var)) + elif is_number(other): + target_c_shape = CShape(*target.shape) + other_dtype = _implicit_dtype(other, target.dtype) + other_array = _constant_array(other, target_c_shape, other_dtype) + safe_call(c_function(ctypes.pointer(out.arr), target.arr, other_array.arr, _bcast_var)) + else: + raise TypeError(f"{type(other)} is not supported and can not be passed to C binary function.") + + return out + + +def _implicit_dtype(value: int | float | bool | complex, array_dtype: Dtype) -> Dtype: + if isinstance(value, bool): + value_dtype = af_bool + if isinstance(value, int): + value_dtype = af_int64 + elif isinstance(value, float): + value_dtype = af_float64 + elif isinstance(value, complex): + value_dtype = af_complex128 + else: + raise TypeError(f"{type(value)} is not supported and can not be converted to af.Dtype.") + + if not (array_dtype == af_float32 or array_dtype == af_complex64): + return value_dtype + + if value_dtype == af_float64: + return af_float32 + + if value_dtype == af_complex128: + return af_complex64 + + return value_dtype + + +def _constant_array(value: int | float | bool | complex, shape: CShape, dtype: Dtype) -> Array: + out = Array() + + if isinstance(value, complex): + if dtype != af_complex64 and dtype != af_complex128: + dtype = af_complex64 + + safe_call(backend.get().af_constant_complex( + ctypes.pointer(out.arr), ctypes.c_double(value.real), ctypes.c_double(value.imag), 4, + ctypes.pointer(shape.c_array), dtype)) + elif dtype == af_int64: + safe_call(backend.get().af_constant_long( + ctypes.pointer(out.arr), ctypes.c_longlong(value.real), 4, ctypes.pointer(shape.c_array))) + elif dtype == af_uint64: + safe_call(backend.get().af_constant_ulong( + ctypes.pointer(out.arr), ctypes.c_ulonglong(value.real), 4, ctypes.pointer(shape.c_array))) + else: + safe_call(backend.get().af_constant( + ctypes.pointer(out.arr), ctypes.c_double(value), 4, ctypes.pointer(shape.c_array), dtype)) + + return out diff --git a/arrayfire/array_api/_utils.py b/arrayfire/array_api/_utils.py index e235480c2..67b06f253 100644 --- a/arrayfire/array_api/_utils.py +++ b/arrayfire/array_api/_utils.py @@ -1,13 +1,6 @@ import ctypes import enum import numbers -from typing import Any - - -class Device(enum.Enum): - # HACK. TODO make it real - cpu = "cpu" - gpu = "gpu" class PointerSource(enum.Enum): @@ -23,5 +16,5 @@ def to_str(c_str: ctypes.c_char_p) -> str: return str(c_str.value.decode("utf-8")) # type: ignore[union-attr] -def is_number(number: Any) -> bool: +def is_number(number: int | float | bool | complex) -> bool: return isinstance(number, numbers.Number) From 8cef7744e454247cab1c1f36135bd71271784715 Mon Sep 17 00:00:00 2001 From: Anton Date: Fri, 27 Jan 2023 19:28:39 +0200 Subject: [PATCH 06/36] Fix array init bug. Add __getitem__. Change pytest for active debug mode --- arrayfire/array_api/_array_object.py | 64 ++++++++++++++----- arrayfire/array_api/pytest.ini | 2 +- .../array_api/tests/test_array_object.py | 18 ++++++ 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index a18309dc6..0d0c76a8a 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -5,8 +5,9 @@ from dataclasses import dataclass from typing import Any -from arrayfire import backend, safe_call # TODO refactoring -from arrayfire.array import _in_display_dims_limit # TODO refactoring +from arrayfire import backend, safe_call # TODO refactor +from arrayfire.algorithm import count # TODO refactor +from arrayfire.array import _get_indices, _in_display_dims_limit # TODO refactor from ._dtypes import CShape, Dtype from ._dtypes import bool as af_bool @@ -37,15 +38,15 @@ class Array: # arrayfire's __radd__() instead of numpy's __add__() __array_priority__ = 30 - # Initialisation - arr = ctypes.c_void_p(0) - def __init__( self, x: None | Array | py_array.array | int | ctypes.c_void_p | list = None, dtype: None | Dtype = None, pointer_source: PointerSource = PointerSource.host, shape: None | ShapeType = None, offset: None | ctypes._SimpleCData[int] = None, strides: None | ShapeType = None) -> None: _no_initial_dtype = False # HACK, FIXME + # Initialise array object + self.arr = ctypes.c_void_p(0) + if isinstance(dtype, str): dtype = _str_to_dtype(dtype) @@ -127,7 +128,7 @@ def __str__(self) -> str: # FIXME if not _in_display_dims_limit(self.shape): return _metadata_string(self.dtype, self.shape) - return _metadata_string(self.dtype) + self._as_str() + return _metadata_string(self.dtype) + _array_as_str(self) def __repr__(self) -> str: # FIXME return _metadata_string(self.dtype, self.shape) @@ -173,6 +174,7 @@ def __truediv__(self, other: int | float | bool | complex | Array, /) -> Array: return _process_c_function(self, other, backend.get().af_div) def __floordiv__(self, other: int | float | bool | complex | Array, /) -> Array: + # TODO return NotImplemented def __mod__(self, other: int | float | bool | complex | Array, /) -> Array: @@ -187,6 +189,25 @@ def __pow__(self, other: int | float | bool | complex | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_pow) + def __matmul__(self, other: Array, /) -> Array: + # TODO + return NotImplemented + + def __getitem__(self, key: int | slice | tuple[int | slice] | Array, /) -> Array: + # TODO: API Specification - key: int | slice | ellipsis | tuple[int | slice] | Array + # TODO: refactor + out = Array() + ndims = self.ndim + + if isinstance(key, Array) and key == af_bool.c_api_value: + ndims = 1 + if count(key) == 0: + return out + + safe_call(backend.get().af_index_gen( + ctypes.pointer(out.arr), self.arr, c_dim_t(ndims), _get_indices(key).pointer)) + return out + @property def dtype(self) -> Dtype: out = ctypes.c_int() @@ -234,13 +255,23 @@ def shape(self) -> ShapeType: ctypes.pointer(d0), ctypes.pointer(d1), ctypes.pointer(d2), ctypes.pointer(d3), self.arr)) return (d0.value, d1.value, d2.value, d3.value)[:self.ndim] # Skip passing None values - def _as_str(self) -> str: - arr_str = ctypes.c_char_p(0) - # FIXME add description to passed arguments - safe_call(backend.get().af_array_to_string(ctypes.pointer(arr_str), "", self.arr, 4, True)) - py_str = to_str(arr_str) - safe_call(backend.get().af_free_host(arr_str)) - return py_str + def scalar(self) -> int | float | bool | complex: + """ + Return the first element of the array + """ + # BUG seg fault on empty array + out = self.dtype.c_type() + safe_call(backend.get().af_get_scalar(ctypes.pointer(out), self.arr)) + return out.value # type: ignore[no-any-return] # FIXME + + +def _array_as_str(array: Array) -> str: + arr_str = ctypes.c_char_p(0) + # FIXME add description to passed arguments + safe_call(backend.get().af_array_to_string(ctypes.pointer(arr_str), "", array.arr, 4, True)) + py_str = to_str(arr_str) + safe_call(backend.get().af_free_host(arr_str)) + return py_str def _metadata_string(dtype: Dtype, dims: None | ShapeType = None) -> str: @@ -283,9 +314,8 @@ def _process_c_function( if isinstance(other, Array): safe_call(c_function(ctypes.pointer(out.arr), target.arr, other.arr, _bcast_var)) elif is_number(other): - target_c_shape = CShape(*target.shape) other_dtype = _implicit_dtype(other, target.dtype) - other_array = _constant_array(other, target_c_shape, other_dtype) + other_array = _constant_array(other, CShape(*target.shape), other_dtype) safe_call(c_function(ctypes.pointer(out.arr), target.arr, other_array.arr, _bcast_var)) else: raise TypeError(f"{type(other)} is not supported and can not be passed to C binary function.") @@ -326,7 +356,7 @@ def _constant_array(value: int | float | bool | complex, shape: CShape, dtype: D safe_call(backend.get().af_constant_complex( ctypes.pointer(out.arr), ctypes.c_double(value.real), ctypes.c_double(value.imag), 4, - ctypes.pointer(shape.c_array), dtype)) + ctypes.pointer(shape.c_array), dtype.c_api_value)) elif dtype == af_int64: safe_call(backend.get().af_constant_long( ctypes.pointer(out.arr), ctypes.c_longlong(value.real), 4, ctypes.pointer(shape.c_array))) @@ -335,6 +365,6 @@ def _constant_array(value: int | float | bool | complex, shape: CShape, dtype: D ctypes.pointer(out.arr), ctypes.c_ulonglong(value.real), 4, ctypes.pointer(shape.c_array))) else: safe_call(backend.get().af_constant( - ctypes.pointer(out.arr), ctypes.c_double(value), 4, ctypes.pointer(shape.c_array), dtype)) + ctypes.pointer(out.arr), ctypes.c_double(value), 4, ctypes.pointer(shape.c_array), dtype.c_api_value)) return out diff --git a/arrayfire/array_api/pytest.ini b/arrayfire/array_api/pytest.ini index 43feed854..7fd828bec 100644 --- a/arrayfire/array_api/pytest.ini +++ b/arrayfire/array_api/pytest.ini @@ -1,4 +1,4 @@ [pytest] -addopts = --cache-clear --cov=./arrayfire/array_api --flake8 --isort ./arrayfire/array_api +addopts = --cache-clear --cov=./arrayfire/array_api --flake8 --isort -s ./arrayfire/array_api console_output_style = classic markers = mypy diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index e52f83556..028e1fe6a 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -93,3 +93,21 @@ def test_array_from_unsupported_type() -> None: with pytest.raises(TypeError): Array({1: 2, 3: 4}) # type: ignore[arg-type] + + +def test_array_getitem() -> None: + array = Array([1, 2, 3, 4, 5]) + + int_item = array[2] + assert array.dtype == int_item.dtype + assert int_item.scalar() == 3 + + # TODO add more tests for different dtypes + + +# def test_array_sum() -> None: # BUG no element-wise adding +# array = Array([1, 2, 3]) +# res = array + 1 +# assert res.scalar() == 2 +# assert res.scalar() == 3 +# assert res.scalar() == 4 From a4c7ac9cf45d5b8a92f3c02fcad81cfb2034df4e Mon Sep 17 00:00:00 2001 From: Anton Date: Fri, 27 Jan 2023 19:37:05 +0200 Subject: [PATCH 07/36] Add reflected arithmetic and array operators --- arrayfire/array_api/_array_object.py | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index 0d0c76a8a..e51207db0 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -193,6 +193,51 @@ def __matmul__(self, other: Array, /) -> Array: # TODO return NotImplemented + def __radd__(self, other: Array, /) -> Array: + # TODO discuss either we need to support complex and bool as other input type + """ + Return other + self. + """ + return _process_c_function(other, self, backend.get().af_add) + + def __rsub__(self, other: Array, /) -> Array: + """ + Return other - self. + """ + return _process_c_function(other, self, backend.get().af_sub) + + def __rmul__(self, other: Array, /) -> Array: + """ + Return other * self. + """ + return _process_c_function(other, self, backend.get().af_mul) + + def __rtruediv__(self, other: Array, /) -> Array: + """ + Return other / self. + """ + return _process_c_function(other, self, backend.get().af_div) + + def __rfloordiv__(self, other: Array, /) -> Array: + # TODO + return NotImplemented + + def __rmod__(self, other: Array, /) -> Array: + """ + Return other / self. + """ + return _process_c_function(other, self, backend.get().af_mod) + + def __rpow__(self, other: Array, /) -> Array: + """ + Return other ** self. + """ + return _process_c_function(other, self, backend.get().af_pow) + + def __rmatmul__(self, other: Array, /) -> Array: + # TODO + return NotImplemented + def __getitem__(self, key: int | slice | tuple[int | slice] | Array, /) -> Array: # TODO: API Specification - key: int | slice | ellipsis | tuple[int | slice] | Array # TODO: refactor From 41405279484aeb01d67db5fb39b2fc59cbe8f8e6 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 28 Jan 2023 16:28:09 +0200 Subject: [PATCH 08/36] Place TODO for repr --- arrayfire/array_api/_array_object.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index e51207db0..f5fcf4744 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -131,7 +131,9 @@ def __str__(self) -> str: # FIXME return _metadata_string(self.dtype) + _array_as_str(self) def __repr__(self) -> str: # FIXME - return _metadata_string(self.dtype, self.shape) + # return _metadata_string(self.dtype, self.shape) + # TODO change the look of array representation. E.g., like np.array + return _array_as_str(self) def __len__(self) -> int: return self.shape[0] if self.shape else 0 # type: ignore[return-value] From 4374d9397aaa3446ecf4f3d96529ff49cbd2f31c Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 28 Jan 2023 16:53:23 +0200 Subject: [PATCH 09/36] Add bitwise operators. Add in-place operators. Add missing reflected operators --- arrayfire/array_api/_array_object.py | 204 ++++++++++++++++++++++++++- 1 file changed, 202 insertions(+), 2 deletions(-) diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index f5fcf4744..d6b5a07b9 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -87,7 +87,7 @@ def __init__( if _no_initial_dtype: raise TypeError("Expected to receive the initial dtype due to the x being a data pointer.") - _type_char = dtype.typecode + _type_char = dtype.typecode # type: ignore[assignment] # FIXME else: raise TypeError("Passed object x is an object of unsupported class.") @@ -138,6 +138,8 @@ def __repr__(self) -> str: # FIXME def __len__(self) -> int: return self.shape[0] if self.shape else 0 # type: ignore[return-value] + # Arithmetic Operators + def __pos__(self) -> Array: """ Return +self @@ -148,7 +150,7 @@ def __neg__(self) -> Array: """ Return -self """ - return 0 - self + return 0 - self # type: ignore[no-any-return, operator] # FIXME def __add__(self, other: int | float | Array, /) -> Array: # TODO discuss either we need to support complex and bool as other input type @@ -191,10 +193,92 @@ def __pow__(self, other: int | float | bool | complex | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_pow) + # Array Operators + def __matmul__(self, other: Array, /) -> Array: # TODO return NotImplemented + # Bitwise Operators + + def __invert__(self) -> Array: + """ + Return ~self. + """ + out = Array() + safe_call(backend.get().af_bitnot(ctypes.pointer(out.arr), self.arr)) + return out + + def __and__(self, other: int | bool | Array, /) -> Array: + """ + Return self & other. + """ + return _process_c_function(self, other, backend.get().af_bitand) + + def __or__(self, other: int | bool | Array, /) -> Array: + """ + Return self | other. + """ + return _process_c_function(self, other, backend.get().af_bitor) + + def __xor__(self, other: int | bool | Array, /) -> Array: + """ + Return self ^ other. + """ + return _process_c_function(self, other, backend.get().af_bitxor) + + def __lshift__(self, other: int | Array, /) -> Array: + """ + Return self << other. + """ + return _process_c_function(self, other, backend.get().af_bitshiftl) + + def __rshift__(self, other: int | Array, /) -> Array: + """ + Return self >> other. + """ + return _process_c_function(self, other, backend.get().af_bitshiftr) + + # Comparison Operators + + def __lt__(self, other: int | float | Array, /) -> Array: + """ + Return self < other. + """ + return _process_c_function(self, other, backend.get().af_lt) + + def __le__(self, other: int | float | Array, /) -> Array: + """ + Return self <= other. + """ + return _process_c_function(self, other, backend.get().af_le) + + def __gt__(self, other: int | float | Array, /) -> Array: + """ + Return self > other. + """ + return _process_c_function(self, other, backend.get().af_gt) + + def __ge__(self, other: int | float | Array, /) -> Array: + """ + Return self >= other. + """ + return _process_c_function(self, other, backend.get().af_ge) + + def __eq__(self, other: int | float | bool | Array, /) -> Array: # type: ignore[override] # FIXME + """ + Return self == other. + """ + return _process_c_function(self, other, backend.get().af_eq) + + def __ne__(self, other: int | float | bool | Array, /) -> Array: # type: ignore[override] # FIXME + """ + Return self != other. + """ + return _process_c_function(self, other, backend.get().af_neq) + + # Reflected Arithmetic Operators + def __radd__(self, other: Array, /) -> Array: # TODO discuss either we need to support complex and bool as other input type """ @@ -236,10 +320,125 @@ def __rpow__(self, other: Array, /) -> Array: """ return _process_c_function(other, self, backend.get().af_pow) + # Reflected Array Operators + def __rmatmul__(self, other: Array, /) -> Array: # TODO return NotImplemented + # Reflected Bitwise Operators + + def __rand__(self, other: Array, /) -> Array: + """ + Return other & self. + """ + return _process_c_function(other, self, backend.get().af_bitand) + + def __ror__(self, other: Array, /) -> Array: + """ + Return other & self. + """ + return _process_c_function(other, self, backend.get().af_bitor) + + def __rxor__(self, other: Array, /) -> Array: + """ + Return other ^ self. + """ + return _process_c_function(other, self, backend.get().af_bitxor) + + def __rlshift__(self, other: Array, /) -> Array: + """ + Return other << self. + """ + return _process_c_function(other, self, backend.get().af_bitshiftl) + + def __rrshift__(self, other: Array, /) -> Array: + """ + Return other >> self. + """ + return _process_c_function(other, self, backend.get().af_bitshiftr) + + # In-place Arithmetic Operators + + def __iadd__(self, other: int | float | Array, /) -> Array: + # TODO discuss either we need to support complex and bool as other input type + """ + Return self += other. + """ + return _process_c_function(self, other, backend.get().af_add) + + def __isub__(self, other: int | float | bool | complex | Array, /) -> Array: + """ + Return self -= other. + """ + return _process_c_function(self, other, backend.get().af_sub) + + def __imul__(self, other: int | float | bool | complex | Array, /) -> Array: + """ + Return self *= other. + """ + return _process_c_function(self, other, backend.get().af_mul) + + def __itruediv__(self, other: int | float | bool | complex | Array, /) -> Array: + """ + Return self /= other. + """ + return _process_c_function(self, other, backend.get().af_div) + + def __ifloordiv__(self, other: int | float | bool | complex | Array, /) -> Array: + # TODO + return NotImplemented + + def __imod__(self, other: int | float | bool | complex | Array, /) -> Array: + """ + Return self %= other. + """ + return _process_c_function(self, other, backend.get().af_mod) + + def __ipow__(self, other: int | float | bool | complex | Array, /) -> Array: + """ + Return self **= other. + """ + return _process_c_function(self, other, backend.get().af_pow) + + # In-place Array Operators + + def __imatmul__(self, other: Array, /) -> Array: + # TODO + return NotImplemented + + # In-place Bitwise Operators + + def __iand__(self, other: int | bool | Array, /) -> Array: + """ + Return self &= other. + """ + return _process_c_function(self, other, backend.get().af_bitand) + + def __ior__(self, other: int | bool | Array, /) -> Array: + """ + Return self |= other. + """ + return _process_c_function(self, other, backend.get().af_bitor) + + def __ixor__(self, other: int | bool | Array, /) -> Array: + """ + Return self ^= other. + """ + return _process_c_function(self, other, backend.get().af_bitxor) + + def __ilshift__(self, other: int | Array, /) -> Array: + """ + Return self <<= other. + """ + return _process_c_function(self, other, backend.get().af_bitshiftl) + + def __irshift__(self, other: int | Array, /) -> Array: + """ + Return self >>= other. + """ + return _process_c_function(self, other, backend.get().af_bitshiftr) + def __getitem__(self, key: int | slice | tuple[int | slice] | Array, /) -> Array: # TODO: API Specification - key: int | slice | ellipsis | tuple[int | slice] | Array # TODO: refactor @@ -405,6 +604,7 @@ def _constant_array(value: int | float | bool | complex, shape: CShape, dtype: D ctypes.pointer(out.arr), ctypes.c_double(value.real), ctypes.c_double(value.imag), 4, ctypes.pointer(shape.c_array), dtype.c_api_value)) elif dtype == af_int64: + # TODO discuss workaround for passing float to ctypes safe_call(backend.get().af_constant_long( ctypes.pointer(out.arr), ctypes.c_longlong(value.real), 4, ctypes.pointer(shape.c_array))) elif dtype == af_uint64: From 5a29ffa69fbd59b6f8cc972faae1cfa7487f5d9e Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 28 Jan 2023 16:55:34 +0200 Subject: [PATCH 10/36] Fix tests --- arrayfire/array_api/tests/test_array_object.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index 028e1fe6a..769d5f175 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -105,9 +105,9 @@ def test_array_getitem() -> None: # TODO add more tests for different dtypes -# def test_array_sum() -> None: # BUG no element-wise adding -# array = Array([1, 2, 3]) -# res = array + 1 -# assert res.scalar() == 2 -# assert res.scalar() == 3 -# assert res.scalar() == 4 +def test_array_sum() -> None: + array = Array([1, 2, 3]) + res = array + 1 + assert res[0].scalar() == 2 + assert res[1].scalar() == 3 + assert res[2].scalar() == 4 From 4187b27b9b39df811f5069184246b1572ce61667 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 28 Jan 2023 17:23:28 +0200 Subject: [PATCH 11/36] Add tests for arithmetic operators --- arrayfire/array_api/_array_object.py | 12 +- .../array_api/tests/test_array_object.py | 105 ++++++++++++++++++ 2 files changed, 111 insertions(+), 6 deletions(-) diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index d6b5a07b9..308f1e5ea 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -159,35 +159,35 @@ def __add__(self, other: int | float | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_add) - def __sub__(self, other: int | float | bool | complex | Array, /) -> Array: + def __sub__(self, other: int | float | Array, /) -> Array: """ Return self - other. """ return _process_c_function(self, other, backend.get().af_sub) - def __mul__(self, other: int | float | bool | complex | Array, /) -> Array: + def __mul__(self, other: int | float | Array, /) -> Array: """ Return self * other. """ return _process_c_function(self, other, backend.get().af_mul) - def __truediv__(self, other: int | float | bool | complex | Array, /) -> Array: + def __truediv__(self, other: int | float | Array, /) -> Array: """ Return self / other. """ return _process_c_function(self, other, backend.get().af_div) - def __floordiv__(self, other: int | float | bool | complex | Array, /) -> Array: + def __floordiv__(self, other: int | float | Array, /) -> Array: # TODO return NotImplemented - def __mod__(self, other: int | float | bool | complex | Array, /) -> Array: + def __mod__(self, other: int | float | Array, /) -> Array: """ Return self % other. """ return _process_c_function(self, other, backend.get().af_mod) - def __pow__(self, other: int | float | bool | complex | Array, /) -> Array: + def __pow__(self, other: int | float | Array, /) -> Array: """ Return self ** other. """ diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index 769d5f175..c139f57b6 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -111,3 +111,108 @@ def test_array_sum() -> None: assert res[0].scalar() == 2 assert res[1].scalar() == 3 assert res[2].scalar() == 4 + + res = array + 1.5 + assert res[0].scalar() == 2.5 + assert res[1].scalar() == 3.5 + assert res[2].scalar() == 4.5 + + res = array + Array([9, 9, 9]) + assert res[0].scalar() == 10 + assert res[1].scalar() == 11 + assert res[2].scalar() == 12 + + +def test_array_sub() -> None: + array = Array([1, 2, 3]) + res = array - 1 + assert res[0].scalar() == 0 + assert res[1].scalar() == 1 + assert res[2].scalar() == 2 + + res = array - 1.5 + assert res[0].scalar() == -0.5 + assert res[1].scalar() == 0.5 + assert res[2].scalar() == 1.5 + + res = array - Array([9, 9, 9]) + assert res[0].scalar() == -8 + assert res[1].scalar() == -7 + assert res[2].scalar() == -6 + + +def test_array_mul() -> None: + array = Array([1, 2, 3]) + res = array * 2 + assert res[0].scalar() == 2 + assert res[1].scalar() == 4 + assert res[2].scalar() == 6 + + res = array * 1.5 + assert res[0].scalar() == 1.5 + assert res[1].scalar() == 3 + assert res[2].scalar() == 4.5 + + res = array * Array([9, 9, 9]) + assert res[0].scalar() == 9 + assert res[1].scalar() == 18 + assert res[2].scalar() == 27 + + +def test_array_truediv() -> None: + array = Array([1, 2, 3]) + res = array / 2 + assert res[0].scalar() == 0.5 + assert res[1].scalar() == 1 + assert res[2].scalar() == 1.5 + + res = array / 1.5 + assert round(res[0].scalar(), 5) == 0.66667 # type: ignore[arg-type] + assert round(res[1].scalar(), 5) == 1.33333 # type: ignore[arg-type] + assert res[2].scalar() == 2 + + res = array / Array([2, 2, 2]) + assert res[0].scalar() == 0.5 + assert res[1].scalar() == 1 + assert res[2].scalar() == 1.5 + + +def test_array_floordiv() -> None: + # TODO add test after implementation of __floordiv__ + pass + + +def test_array_mod() -> None: + array = Array([1, 2, 3]) + res = array % 2 + assert res[0].scalar() == 1 + assert res[1].scalar() == 0 + assert res[2].scalar() == 1 + + res = array % 1.5 + assert res[0].scalar() == 1.0 + assert res[1].scalar() == 0.5 + assert res[2].scalar() == 0.0 + + res = array % Array([9, 9, 9]) + assert res[0].scalar() == 1.0 + assert res[1].scalar() == 2.0 + assert res[2].scalar() == 3.0 + + +def test_array_pow() -> None: + array = Array([1, 2, 3]) + res = array ** 2 + assert res[0].scalar() == 1 + assert res[1].scalar() == 4 + assert res[2].scalar() == 9 + + res = array ** 1.5 + assert res[0].scalar() == 1 + assert round(res[1].scalar(), 5) == 2.82843 # type: ignore[arg-type] + assert round(res[2].scalar(), 5) == 5.19615 # type: ignore[arg-type] + + res = array ** Array([9, 9, 9]) + assert res[0].scalar() == 1 + assert res[1].scalar() == 512 + assert res[2].scalar() == 19683 From cdb7a92f3ae8789268123e8d7ca4c32097a749a7 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 28 Jan 2023 21:39:17 +0200 Subject: [PATCH 12/36] Added to_list and to_ctypes_array --- arrayfire/array_api/_array_object.py | 71 +++++++++++++++++-- .../array_api/tests/test_array_object.py | 15 +++- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index 308f1e5ea..1e6d24216 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -124,19 +124,20 @@ def __init__( ctypes.pointer(_cshape.c_array), ctypes.pointer(strides_cshape), dtype.c_api_value, pointer_source.value)) - def __str__(self) -> str: # FIXME + 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) + _array_as_str(self) - def __repr__(self) -> str: # FIXME + def __repr__(self) -> str: # return _metadata_string(self.dtype, self.shape) # TODO change the look of array representation. E.g., like np.array return _array_as_str(self) def __len__(self) -> int: - return self.shape[0] if self.shape else 0 # type: ignore[return-value] + return self.shape[0] if self.shape else 0 # Arithmetic Operators @@ -475,7 +476,7 @@ def T(self) -> Array: raise NotImplementedError @property - def size(self) -> None | int: + def size(self) -> int: # NOTE previously - elements() out = c_dim_t(0) safe_call(backend.get().af_get_elements(ctypes.pointer(out), self.arr)) @@ -483,9 +484,9 @@ def size(self) -> None | int: @property def ndim(self) -> int: - nd = ctypes.c_uint(0) - safe_call(backend.get().af_get_numdims(ctypes.pointer(nd), self.arr)) - return nd.value + out = ctypes.c_uint(0) + safe_call(backend.get().af_get_numdims(ctypes.pointer(out), self.arr)) + return out.value @property def shape(self) -> ShapeType: @@ -510,6 +511,62 @@ def scalar(self) -> int | float | bool | complex: safe_call(backend.get().af_get_scalar(ctypes.pointer(out), self.arr)) return out.value # type: ignore[no-any-return] # FIXME + def is_empty(self) -> bool: + """ + Check if the array is empty i.e. it has no elements. + """ + out = ctypes.c_bool() + safe_call(backend.get().af_is_empty(ctypes.pointer(out), self.arr)) + return out.value + + def to_list(self, row_major: bool = False) -> list: # FIXME return typings + if self.is_empty(): + return [] + + array = _reorder(self) if row_major else self + ctypes_array = _get_ctypes_array(array) + + if array.ndim == 1: + return list(ctypes_array) + + out = [] + for i in range(array.size): + idx = i + sub_list = [] + for j in range(array.ndim): + div = array.shape[j] + sub_list.append(idx % div) + idx //= div + out.append(ctypes_array[sub_list[::-1]]) # type: ignore[call-overload] # FIXME + return out + + def to_ctype_array(self, row_major: bool = False) -> ctypes.Array: + if self.is_empty(): + raise RuntimeError("Can not convert an empty array to ctype.") + + array = _reorder(self) if row_major else self + return _get_ctypes_array(array) + + +def _get_ctypes_array(array: Array) -> ctypes.Array: + c_shape = array.dtype.c_type * array.size + ctypes_array = c_shape() + safe_call(backend.get().af_get_data_ptr(ctypes.pointer(ctypes_array), array.arr)) + return ctypes_array + + +def _reorder(array: Array) -> Array: + """ + Returns a reordered array to help interoperate with row major formats. + """ + if array.ndim == 1: + return array + + out = Array() + c_shape = CShape(*(tuple(reversed(range(array.ndim))) + tuple(range(array.ndim, 4)))) + safe_call(backend.get().af_reorder(ctypes.pointer(out.arr), array.arr, *c_shape)) + return out + def _array_as_str(array: Array) -> str: arr_str = ctypes.c_char_p(0) diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index c139f57b6..7eb0a78e8 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -3,6 +3,8 @@ from arrayfire.array_api import Array, float32, int16 from arrayfire.array_api._dtypes import supported_dtypes +# TODO change separated methods with setup and teardown to avoid code duplication + def test_empty_array() -> None: array = Array() @@ -105,7 +107,13 @@ def test_array_getitem() -> None: # TODO add more tests for different dtypes -def test_array_sum() -> None: +def test_array_to_list() -> None: + # TODO add test of to_ctypes_array + assert Array([1, 2, 3]).to_list() == [1, 2, 3] + assert Array().to_list() == [] + + +def test_array_add() -> None: array = Array([1, 2, 3]) res = array + 1 assert res[0].scalar() == 2 @@ -123,6 +131,11 @@ def test_array_sum() -> None: assert res[2].scalar() == 12 +def test_array_add_raises_type_error() -> None: + with pytest.raises(TypeError): + Array([1, 2, 3]) + "15" # type: ignore[operator] + + def test_array_sub() -> None: array = Array([1, 2, 3]) res = array - 1 From 9c0435a3f7a2b77ccf6aa2a8d75747428aa61ab2 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 28 Jan 2023 21:44:51 +0200 Subject: [PATCH 13/36] Fix bug when scalar is empty returns None --- arrayfire/array_api/_array_object.py | 6 ++++-- .../array_api/tests/test_array_object.py | 20 ++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index 1e6d24216..36fb2cb69 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -502,11 +502,13 @@ def shape(self) -> ShapeType: ctypes.pointer(d0), ctypes.pointer(d1), ctypes.pointer(d2), ctypes.pointer(d3), self.arr)) return (d0.value, d1.value, d2.value, d3.value)[:self.ndim] # Skip passing None values - def scalar(self) -> int | float | bool | complex: + def scalar(self) -> None | int | float | bool | complex: """ Return the first element of the array """ - # BUG seg fault on empty array + if self.is_empty(): + return None + out = self.dtype.c_type() safe_call(backend.get().af_get_scalar(ctypes.pointer(out), self.arr)) return out.value # type: ignore[no-any-return] # FIXME diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index 7eb0a78e8..be80365b1 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -107,10 +107,24 @@ def test_array_getitem() -> None: # TODO add more tests for different dtypes +def test_scalar() -> None: + array = Array([1, 2, 3]) + assert array[1].scalar() == 2 + + +def test_scalar_is_empty() -> None: + array = Array() + assert array.scalar() is None + + def test_array_to_list() -> None: - # TODO add test of to_ctypes_array - assert Array([1, 2, 3]).to_list() == [1, 2, 3] - assert Array().to_list() == [] + array = Array([1, 2, 3]) + assert array.to_list() == [1, 2, 3] + + +def test_array_to_list_is_empty() -> None: + array = Array() + assert array.to_list() == [] def test_array_add() -> None: From 769c16cbcb6dcbd88dccbb8d635d522732a3b9f3 Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 29 Jan 2023 02:01:13 +0200 Subject: [PATCH 14/36] Fix typing in array object. Add tests --- arrayfire/array_api/_array_object.py | 9 ++-- .../array_api/tests/test_array_object.py | 49 +++++++++++++++++-- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index 36fb2cb69..6e13eb330 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -39,16 +39,17 @@ class Array: __array_priority__ = 30 def __init__( - self, x: None | Array | py_array.array | int | ctypes.c_void_p | list = None, dtype: None | Dtype = None, - pointer_source: PointerSource = PointerSource.host, shape: None | ShapeType = None, - offset: None | ctypes._SimpleCData[int] = None, strides: None | ShapeType = None) -> None: + self, x: None | Array | py_array.array | int | ctypes.c_void_p | list = None, + dtype: None | Dtype | str = None, shape: None | ShapeType = None, + pointer_source: PointerSource = PointerSource.host, offset: None | ctypes._SimpleCData[int] = None, + strides: None | ShapeType = None) -> None: _no_initial_dtype = False # HACK, FIXME # Initialise array object self.arr = ctypes.c_void_p(0) if isinstance(dtype, str): - dtype = _str_to_dtype(dtype) + dtype = _str_to_dtype(dtype) # type: ignore[arg-type] if dtype is None: _no_initial_dtype = True diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index be80365b1..dc3b31de0 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -1,12 +1,15 @@ +import array as pyarray + import pytest from arrayfire.array_api import Array, float32, int16 from arrayfire.array_api._dtypes import supported_dtypes # TODO change separated methods with setup and teardown to avoid code duplication +# TODO add tests for array arguments: device, offset, strides -def test_empty_array() -> None: +def test_create_empty_array() -> None: array = Array() assert array.dtype == float32 @@ -16,7 +19,7 @@ def test_empty_array() -> None: assert len(array) == 0 -def test_empty_array_with_nonempty_dtype() -> None: +def test_create_empty_array_with_nonempty_dtype() -> None: array = Array(dtype=int16) assert array.dtype == int16 @@ -26,7 +29,32 @@ def test_empty_array_with_nonempty_dtype() -> None: assert len(array) == 0 -def test_empty_array_with_nonempty_shape() -> None: +def test_create_empty_array_with_str_dtype() -> None: + array = Array(dtype="short int") + + assert array.dtype == int16 + assert array.ndim == 0 + assert array.size == 0 + assert array.shape == () + assert len(array) == 0 + + +def test_create_empty_array_with_literal_dtype() -> None: + array = Array(dtype="h") + + assert array.dtype == int16 + assert array.ndim == 0 + assert array.size == 0 + assert array.shape == () + assert len(array) == 0 + + +def test_create_empty_array_with_not_matching_str_dtype() -> None: + with pytest.raises(TypeError): + Array(dtype="hello world") + + +def test_create_empty_array_with_nonempty_shape() -> None: array = Array(shape=(2, 3)) assert array.dtype == float32 @@ -36,7 +64,7 @@ def test_empty_array_with_nonempty_shape() -> None: assert len(array) == 2 -def test_array_from_1d_list() -> None: +def test_create_array_from_1d_list() -> None: array = Array([1, 2, 3]) assert array.dtype == float32 @@ -46,11 +74,22 @@ def test_array_from_1d_list() -> None: assert len(array) == 3 -def test_array_from_2d_list() -> None: +def test_create_array_from_2d_list() -> None: with pytest.raises(TypeError): Array([[1, 2, 3], [1, 2, 3]]) +def test_create_array_from_pyarray() -> None: + py_array = pyarray.array("f", [1, 2, 3]) + array = Array(py_array) + + assert array.dtype == float32 + assert array.ndim == 1 + assert array.size == 3 + assert array.shape == (3,) + assert len(array) == 3 + + def test_array_from_list_with_unsupported_dtype() -> None: for dtype in supported_dtypes: if dtype == float32: From fb27e469f42453e3e4c18b1125ba546f280ff422 Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 29 Jan 2023 03:16:06 +0200 Subject: [PATCH 15/36] Change tests and found bug with reflected operators --- arrayfire/array_api/_array_object.py | 27 +- .../array_api/tests/test_array_object.py | 392 +++++++++++++----- 2 files changed, 297 insertions(+), 122 deletions(-) diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index 6e13eb330..bb676c3fd 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -36,7 +36,7 @@ class Array: # Setting to such a high value should make sure that arrayfire has priority over # other classes, ensuring that e.g. numpy.float32(1)*arrayfire.randu(3) is handled by # arrayfire's __radd__() instead of numpy's __add__() - __array_priority__ = 30 + __array_priority__ = 30 # TODO discuss its purpose def __init__( self, x: None | Array | py_array.array | int | ctypes.c_void_p | list = None, @@ -286,25 +286,25 @@ def __radd__(self, other: Array, /) -> Array: """ Return other + self. """ - return _process_c_function(other, self, backend.get().af_add) + return _process_c_function(self, other, backend.get().af_add) def __rsub__(self, other: Array, /) -> Array: """ Return other - self. """ - return _process_c_function(other, self, backend.get().af_sub) + return _process_c_function(self, other, backend.get().af_sub) def __rmul__(self, other: Array, /) -> Array: """ Return other * self. """ - return _process_c_function(other, self, backend.get().af_mul) + return _process_c_function(self, other, backend.get().af_mul) def __rtruediv__(self, other: Array, /) -> Array: """ Return other / self. """ - return _process_c_function(other, self, backend.get().af_div) + return _process_c_function(self, other, backend.get().af_div) def __rfloordiv__(self, other: Array, /) -> Array: # TODO @@ -314,13 +314,13 @@ def __rmod__(self, other: Array, /) -> Array: """ Return other / self. """ - return _process_c_function(other, self, backend.get().af_mod) + return _process_c_function(self, other, backend.get().af_mod) def __rpow__(self, other: Array, /) -> Array: """ Return other ** self. """ - return _process_c_function(other, self, backend.get().af_pow) + return _process_c_function(self, other, backend.get().af_pow) # Reflected Array Operators @@ -334,31 +334,31 @@ def __rand__(self, other: Array, /) -> Array: """ Return other & self. """ - return _process_c_function(other, self, backend.get().af_bitand) + return _process_c_function(self, other, backend.get().af_bitand) def __ror__(self, other: Array, /) -> Array: """ Return other & self. """ - return _process_c_function(other, self, backend.get().af_bitor) + return _process_c_function(self, other, backend.get().af_bitor) def __rxor__(self, other: Array, /) -> Array: """ Return other ^ self. """ - return _process_c_function(other, self, backend.get().af_bitxor) + return _process_c_function(self, other, backend.get().af_bitxor) def __rlshift__(self, other: Array, /) -> Array: """ Return other << self. """ - return _process_c_function(other, self, backend.get().af_bitshiftl) + return _process_c_function(self, other, backend.get().af_bitshiftl) def __rrshift__(self, other: Array, /) -> Array: """ Return other >> self. """ - return _process_c_function(other, self, backend.get().af_bitshiftr) + return _process_c_function(self, other, backend.get().af_bitshiftr) # In-place Arithmetic Operators @@ -617,6 +617,9 @@ def _process_c_function( target: Array, other: int | float | bool | complex | Array, c_function: Any) -> Array: out = Array() + # TODO discuss the difference between binary_func and binary_funcr + # because implementation looks like exectly the same. + # consider chaging to __iadd__ = __radd__ = __add__ interfce if no difference if isinstance(other, Array): safe_call(c_function(ctypes.pointer(out.arr), target.arr, other.arr, _bcast_var)) elif is_number(other): diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index dc3b31de0..4f50019ce 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -1,4 +1,5 @@ import array as pyarray +from typing import Any import pytest @@ -7,6 +8,7 @@ # TODO change separated methods with setup and teardown to avoid code duplication # TODO add tests for array arguments: device, offset, strides +# TODO add tests for all supported dtypes on initialisation def test_create_empty_array() -> None: @@ -166,119 +168,289 @@ def test_array_to_list_is_empty() -> None: assert array.to_list() == [] -def test_array_add() -> None: - array = Array([1, 2, 3]) - res = array + 1 - assert res[0].scalar() == 2 - assert res[1].scalar() == 3 - assert res[2].scalar() == 4 - - res = array + 1.5 - assert res[0].scalar() == 2.5 - assert res[1].scalar() == 3.5 - assert res[2].scalar() == 4.5 - - res = array + Array([9, 9, 9]) - assert res[0].scalar() == 10 - assert res[1].scalar() == 11 - assert res[2].scalar() == 12 - - -def test_array_add_raises_type_error() -> None: - with pytest.raises(TypeError): - Array([1, 2, 3]) + "15" # type: ignore[operator] - - -def test_array_sub() -> None: - array = Array([1, 2, 3]) - res = array - 1 - assert res[0].scalar() == 0 - assert res[1].scalar() == 1 - assert res[2].scalar() == 2 - - res = array - 1.5 - assert res[0].scalar() == -0.5 - assert res[1].scalar() == 0.5 - assert res[2].scalar() == 1.5 - - res = array - Array([9, 9, 9]) - assert res[0].scalar() == -8 - assert res[1].scalar() == -7 - assert res[2].scalar() == -6 - - -def test_array_mul() -> None: - array = Array([1, 2, 3]) - res = array * 2 - assert res[0].scalar() == 2 - assert res[1].scalar() == 4 - assert res[2].scalar() == 6 - - res = array * 1.5 - assert res[0].scalar() == 1.5 - assert res[1].scalar() == 3 - assert res[2].scalar() == 4.5 - - res = array * Array([9, 9, 9]) - assert res[0].scalar() == 9 - assert res[1].scalar() == 18 - assert res[2].scalar() == 27 - - -def test_array_truediv() -> None: - array = Array([1, 2, 3]) - res = array / 2 - assert res[0].scalar() == 0.5 - assert res[1].scalar() == 1 - assert res[2].scalar() == 1.5 - - res = array / 1.5 - assert round(res[0].scalar(), 5) == 0.66667 # type: ignore[arg-type] - assert round(res[1].scalar(), 5) == 1.33333 # type: ignore[arg-type] - assert res[2].scalar() == 2 - - res = array / Array([2, 2, 2]) - assert res[0].scalar() == 0.5 - assert res[1].scalar() == 1 - assert res[2].scalar() == 1.5 - - -def test_array_floordiv() -> None: - # TODO add test after implementation of __floordiv__ - pass +class TestArithmeticOperators: + def setup_method(self, method: Any) -> None: + self.list = [1, 2, 3] + self.const_int = 2 + self.const_float = 1.5 + self.array = Array(self.list) + self.array_other = Array([9, 9, 9]) + + self.tuple = (1, 2, 3) + self.const_str = "15" + + def teardown_method(self, method: Any) -> None: + self.array = Array(self.list) + + def test_add_int(self) -> None: + res = self.array + self.const_int + assert res[0].scalar() == 3 + assert res[1].scalar() == 4 + assert res[2].scalar() == 5 + + # Test __add__, __iadd__, __radd__ + + def test_add_float(self) -> None: + res = self.array + self.const_float + assert res[0].scalar() == 2.5 + assert res[1].scalar() == 3.5 + assert res[2].scalar() == 4.5 + + def test_add_array(self) -> None: + res = self.array + self.array_other + assert res[0].scalar() == 10 + assert res[1].scalar() == 11 + assert res[2].scalar() == 12 + + def test_add_inplace_and_reflected(self) -> None: + res = self.array + self.const_int + ires = self.array + ires += self.const_int + rres = self.const_int + self.array # type: ignore[operator] + + assert res[0].scalar() == ires[0].scalar() == rres[0].scalar() == 3 + assert res[1].scalar() == ires[1].scalar() == rres[1].scalar() == 4 + assert res[2].scalar() == ires[2].scalar() == rres[2].scalar() == 5 + + assert res.dtype == ires.dtype == rres.dtype + assert res.ndim == ires.ndim == rres.ndim + assert res.size == ires.size == ires.size + assert res.shape == ires.shape == rres.shape + assert len(res) == len(ires) == len(rres) + + def test_add_raises_type_error(self) -> None: + with pytest.raises(TypeError): + Array([1, 2, 3]) + self.const_str # type: ignore[operator] + with pytest.raises(TypeError): + Array([1, 2, 3]) + self.tuple # type: ignore[operator] + + # Test __sub__, __isub__, __rsub__ + + def test_sub_int(self) -> None: + res = self.array - self.const_int + assert res[0].scalar() == -1 + assert res[1].scalar() == 0 + assert res[2].scalar() == 1 + + def test_sub_float(self) -> None: + res = self.array - self.const_float + assert res[0].scalar() == -0.5 + assert res[1].scalar() == 0.5 + assert res[2].scalar() == 1.5 + + def test_sub_arr(self) -> None: + res = self.array - self.array_other + assert res[0].scalar() == -8 + assert res[1].scalar() == -7 + assert res[2].scalar() == -6 + + def test_sub_inplace_and_reflected(self) -> None: + res = self.array - self.const_int + ires = self.array + ires -= self.const_int + rres = self.const_int - self.array # type: ignore[operator] + + assert res[0].scalar() == ires[0].scalar() == rres[0].scalar() == -1 + assert res[1].scalar() == ires[1].scalar() == rres[1].scalar() == 0 + assert res[2].scalar() == ires[2].scalar() == rres[2].scalar() == 1 + + assert res.dtype == ires.dtype == rres.dtype + assert res.ndim == ires.ndim == rres.ndim + assert res.size == ires.size == ires.size + assert res.shape == ires.shape == rres.shape + assert len(res) == len(ires) == len(rres) + + def test_sub_raises_type_error(self) -> None: + with pytest.raises(TypeError): + self.array - self.const_str # type: ignore[operator] -def test_array_mod() -> None: - array = Array([1, 2, 3]) - res = array % 2 - assert res[0].scalar() == 1 - assert res[1].scalar() == 0 - assert res[2].scalar() == 1 + with pytest.raises(TypeError): + self.array - self.tuple # type: ignore[operator] + + # Test __mul__, __imul__, __rmul__ + + def test_mul_int(self) -> None: + res = self.array * self.const_int + assert res[0].scalar() == 2 + assert res[1].scalar() == 4 + assert res[2].scalar() == 6 + + def test_mul_float(self) -> None: + res = self.array * self.const_float + assert res[0].scalar() == 1.5 + assert res[1].scalar() == 3 + assert res[2].scalar() == 4.5 + + def test_mul_array(self) -> None: + res = self.array * self.array_other + assert res[0].scalar() == 9 + assert res[1].scalar() == 18 + assert res[2].scalar() == 27 + + def test_mul_inplace_and_reflected(self) -> None: + res = self.array * self.const_int + ires = self.array + ires *= self.const_int + rres = self.const_int * self.array # type: ignore[operator] + + assert res[0].scalar() == ires[0].scalar() == rres[0].scalar() == 2 + assert res[1].scalar() == ires[1].scalar() == rres[1].scalar() == 4 + assert res[2].scalar() == ires[2].scalar() == rres[2].scalar() == 6 + + assert res.dtype == ires.dtype == rres.dtype + assert res.ndim == ires.ndim == rres.ndim + assert res.size == ires.size == ires.size + assert res.shape == ires.shape == rres.shape + assert len(res) == len(ires) == len(rres) + + def test_mul_raises_type_error(self) -> None: + with pytest.raises(TypeError): + self.array * self.const_str # type: ignore[operator] - res = array % 1.5 - assert res[0].scalar() == 1.0 - assert res[1].scalar() == 0.5 - assert res[2].scalar() == 0.0 + with pytest.raises(TypeError): + self.array * self.tuple # type: ignore[operator] + + # Test __truediv__, __itruediv__, __rtruediv__ + + def test_truediv_int(self) -> None: + res = self.array / self.const_int + assert res[0].scalar() == 0.5 + assert res[1].scalar() == 1 + assert res[2].scalar() == 1.5 + + def test_truediv_float(self) -> None: + res = self.array / self.const_float + assert round(res[0].scalar(), 5) == 0.66667 # type: ignore[arg-type] + assert round(res[1].scalar(), 5) == 1.33333 # type: ignore[arg-type] + assert res[2].scalar() == 2 + + def test_truediv_array(self) -> None: + res = self.array / self.array_other + assert round(res[0].scalar(), 5) == 0.11111 # type: ignore[arg-type] + assert round(res[1].scalar(), 5) == 0.22222 # type: ignore[arg-type] + assert round(res[2].scalar(), 5) == 0.33333 # type: ignore[arg-type] + + def test_truediv_inplace_and_reflected(self) -> None: + res = self.array / self.const_int + ires = self.array + ires /= self.const_int + rres = self.const_int / self.array # type: ignore[operator] + + assert res[0].scalar() == ires[0].scalar() == 0.5 + assert res[1].scalar() == ires[1].scalar() == 1 + assert res[2].scalar() == ires[2].scalar() == 1.5 + + assert rres[0].scalar() == 2 + assert rres[1].scalar() == 1 + assert round(rres[2].scalar(), 5) == 0.66667 + + assert res.dtype == ires.dtype == rres.dtype + assert res.ndim == ires.ndim == rres.ndim + assert res.size == ires.size == ires.size + assert res.shape == ires.shape == rres.shape + assert len(res) == len(ires) == len(rres) + + def test_truediv_raises_type_error(self) -> None: + with pytest.raises(TypeError): + self.array / self.const_str # type: ignore[operator] - res = array % Array([9, 9, 9]) - assert res[0].scalar() == 1.0 - assert res[1].scalar() == 2.0 - assert res[2].scalar() == 3.0 + with pytest.raises(TypeError): + self.array / self.tuple # type: ignore[operator] + + # TODO + # Test __floordiv__, __ifloordiv__, __rfloordiv__ + + # Test __mod__, __imod__, __rmod__ + + def test_mod_int(self) -> None: + res = self.array % self.const_int + assert res[0].scalar() == 1 + assert res[1].scalar() == 0 + assert res[2].scalar() == 1 + + def test_mod_float(self) -> None: + res = self.array % self.const_float + assert res[0].scalar() == 1.0 + assert res[1].scalar() == 0.5 + assert res[2].scalar() == 0.0 + + def test_mod_array(self) -> None: + res = self.array % self.array_other + assert res[0].scalar() == 1.0 + assert res[1].scalar() == 2.0 + assert res[2].scalar() == 3.0 + + def test_mod_inplace_and_reflected(self) -> None: + res = self.array % self.const_int + ires = self.array + ires %= self.const_int + rres = self.const_int % self.array # type: ignore[operator] + + assert res[0].scalar() == ires[0].scalar() == 1 + assert res[1].scalar() == ires[1].scalar() == 0 + assert res[2].scalar() == ires[2].scalar() == 1 + + assert rres[0].scalar() == 0 + assert rres[1].scalar() == 0 + assert rres[2].scalar() == 2 + + assert res.dtype == ires.dtype == rres.dtype + assert res.ndim == ires.ndim == rres.ndim + assert res.size == ires.size == ires.size + assert res.shape == ires.shape == rres.shape + assert len(res) == len(ires) == len(rres) + + def test_mod_raises_type_error(self) -> None: + with pytest.raises(TypeError): + self.array % self.const_str # type: ignore[operator] + with pytest.raises(TypeError): + self.array % self.tuple # type: ignore[operator] + + # Test __pow__, __ipow__, __rpow__ + + def test_pow_int(self) -> None: + res = self.array ** self.const_int + assert res[0].scalar() == 1 + assert res[1].scalar() == 4 + assert res[2].scalar() == 9 + + def test_pow_float(self) -> None: + res = self.array ** self.const_float + assert res[0].scalar() == 1 + assert round(res[1].scalar(), 5) == 2.82843 # type: ignore[arg-type] + assert round(res[2].scalar(), 5) == 5.19615 # type: ignore[arg-type] + + def test_pow_array(self) -> None: + res = self.array ** self.array_other + assert res[0].scalar() == 1 + assert res[1].scalar() == 512 + assert res[2].scalar() == 19683 + + def test_pow_inplace_and_reflected(self) -> None: + res = self.array ** self.const_int + ires = self.array + ires **= self.const_int + rres = self.const_int ** self.array # type: ignore[operator] + + assert res[0].scalar() == ires[0].scalar() == 1 + assert res[1].scalar() == ires[1].scalar() == 4 + assert res[2].scalar() == ires[2].scalar() == 9 + + assert rres[0].scalar() == 2 + assert rres[1].scalar() == 4 + assert rres[2].scalar() == 8 + + assert res.dtype == ires.dtype == rres.dtype + assert res.ndim == ires.ndim == rres.ndim + assert res.size == ires.size == ires.size + assert res.shape == ires.shape == rres.shape + assert len(res) == len(ires) == len(rres) + + def test_pow_raises_type_error(self) -> None: + with pytest.raises(TypeError): + self.array % self.const_str # type: ignore[operator] -def test_array_pow() -> None: - array = Array([1, 2, 3]) - res = array ** 2 - assert res[0].scalar() == 1 - assert res[1].scalar() == 4 - assert res[2].scalar() == 9 - - res = array ** 1.5 - assert res[0].scalar() == 1 - assert round(res[1].scalar(), 5) == 2.82843 # type: ignore[arg-type] - assert round(res[2].scalar(), 5) == 5.19615 # type: ignore[arg-type] - - res = array ** Array([9, 9, 9]) - assert res[0].scalar() == 1 - assert res[1].scalar() == 512 - assert res[2].scalar() == 19683 + with pytest.raises(TypeError): + self.array % self.tuple # type: ignore[operator] From 0afb92eeb047e0b60cbe1eeeb05cf8182f8084fe Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 29 Jan 2023 04:15:34 +0200 Subject: [PATCH 16/36] Fix reflected operators bug. Add test coverage for the rest of the arithmetic operators --- arrayfire/array_api/_array_object.py | 58 +++++++++++-------- arrayfire/array_api/_utils.py | 5 -- .../array_api/tests/test_array_object.py | 17 +++--- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index bb676c3fd..6232baca6 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -19,7 +19,7 @@ from ._dtypes import int64 as af_int64 from ._dtypes import supported_dtypes from ._dtypes import uint64 as af_uint64 -from ._utils import PointerSource, is_number, to_str +from ._utils import PointerSource, to_str ShapeType = tuple[int, ...] _bcast_var = False # HACK, TODO replace for actual bcast_var after refactoring @@ -286,25 +286,25 @@ def __radd__(self, other: Array, /) -> Array: """ Return other + self. """ - return _process_c_function(self, other, backend.get().af_add) + return _process_c_function(other, self, backend.get().af_add) def __rsub__(self, other: Array, /) -> Array: """ Return other - self. """ - return _process_c_function(self, other, backend.get().af_sub) + return _process_c_function(other, self, backend.get().af_sub) def __rmul__(self, other: Array, /) -> Array: """ Return other * self. """ - return _process_c_function(self, other, backend.get().af_mul) + return _process_c_function(other, self, backend.get().af_mul) def __rtruediv__(self, other: Array, /) -> Array: """ Return other / self. """ - return _process_c_function(self, other, backend.get().af_div) + return _process_c_function(other, self, backend.get().af_div) def __rfloordiv__(self, other: Array, /) -> Array: # TODO @@ -314,13 +314,13 @@ def __rmod__(self, other: Array, /) -> Array: """ Return other / self. """ - return _process_c_function(self, other, backend.get().af_mod) + return _process_c_function(other, self, backend.get().af_mod) def __rpow__(self, other: Array, /) -> Array: """ Return other ** self. """ - return _process_c_function(self, other, backend.get().af_pow) + return _process_c_function(other, self, backend.get().af_pow) # Reflected Array Operators @@ -334,31 +334,31 @@ def __rand__(self, other: Array, /) -> Array: """ Return other & self. """ - return _process_c_function(self, other, backend.get().af_bitand) + return _process_c_function(other, self, backend.get().af_bitand) def __ror__(self, other: Array, /) -> Array: """ Return other & self. """ - return _process_c_function(self, other, backend.get().af_bitor) + return _process_c_function(other, self, backend.get().af_bitor) def __rxor__(self, other: Array, /) -> Array: """ Return other ^ self. """ - return _process_c_function(self, other, backend.get().af_bitxor) + return _process_c_function(other, self, backend.get().af_bitxor) def __rlshift__(self, other: Array, /) -> Array: """ Return other << self. """ - return _process_c_function(self, other, backend.get().af_bitshiftl) + return _process_c_function(other, self, backend.get().af_bitshiftl) def __rrshift__(self, other: Array, /) -> Array: """ Return other >> self. """ - return _process_c_function(self, other, backend.get().af_bitshiftr) + return _process_c_function(other, self, backend.get().af_bitshiftr) # In-place Arithmetic Operators @@ -614,20 +614,32 @@ def _str_to_dtype(value: int) -> Dtype: def _process_c_function( - target: Array, other: int | float | bool | complex | Array, c_function: Any) -> Array: + lhs: int | float | bool | complex | Array, rhs: int | float | bool | complex | Array, + c_function: Any) -> Array: out = Array() - # TODO discuss the difference between binary_func and binary_funcr - # because implementation looks like exectly the same. - # consider chaging to __iadd__ = __radd__ = __add__ interfce if no difference - if isinstance(other, Array): - safe_call(c_function(ctypes.pointer(out.arr), target.arr, other.arr, _bcast_var)) - elif is_number(other): - other_dtype = _implicit_dtype(other, target.dtype) - other_array = _constant_array(other, CShape(*target.shape), other_dtype) - safe_call(c_function(ctypes.pointer(out.arr), target.arr, other_array.arr, _bcast_var)) + if isinstance(lhs, Array) and isinstance(rhs, Array): + lhs_array = lhs.arr + rhs_array = rhs.arr + + elif isinstance(lhs, Array) and isinstance(rhs, int | float | bool | complex): + rhs_dtype = _implicit_dtype(rhs, lhs.dtype) + rhs_constant_array = _constant_array(rhs, CShape(*lhs.shape), rhs_dtype) + + lhs_array = lhs.arr + rhs_array = rhs_constant_array.arr + + elif isinstance(lhs, int | float | bool | complex) and isinstance(rhs, Array): + lhs_dtype = _implicit_dtype(lhs, rhs.dtype) + lhs_constant_array = _constant_array(lhs, CShape(*rhs.shape), lhs_dtype) + + lhs_array = lhs_constant_array.arr + rhs_array = rhs.arr + else: - raise TypeError(f"{type(other)} is not supported and can not be passed to C binary function.") + raise TypeError(f"{type(rhs)} is not supported and can not be passed to C binary function.") + + safe_call(c_function(ctypes.pointer(out.arr), lhs_array, rhs_array, _bcast_var)) return out diff --git a/arrayfire/array_api/_utils.py b/arrayfire/array_api/_utils.py index 67b06f253..15a8a2564 100644 --- a/arrayfire/array_api/_utils.py +++ b/arrayfire/array_api/_utils.py @@ -1,6 +1,5 @@ import ctypes import enum -import numbers class PointerSource(enum.Enum): @@ -14,7 +13,3 @@ class PointerSource(enum.Enum): def to_str(c_str: ctypes.c_char_p) -> str: return str(c_str.value.decode("utf-8")) # type: ignore[union-attr] - - -def is_number(number: int | float | bool | complex) -> bool: - return isinstance(number, numbers.Number) diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index 4f50019ce..44d033fd8 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -179,9 +179,6 @@ def setup_method(self, method: Any) -> None: self.tuple = (1, 2, 3) self.const_str = "15" - def teardown_method(self, method: Any) -> None: - self.array = Array(self.list) - def test_add_int(self) -> None: res = self.array + self.const_int assert res[0].scalar() == 3 @@ -220,10 +217,10 @@ def test_add_inplace_and_reflected(self) -> None: def test_add_raises_type_error(self) -> None: with pytest.raises(TypeError): - Array([1, 2, 3]) + self.const_str # type: ignore[operator] + self.array + self.const_str # type: ignore[operator] with pytest.raises(TypeError): - Array([1, 2, 3]) + self.tuple # type: ignore[operator] + self.array + self.tuple # type: ignore[operator] # Test __sub__, __isub__, __rsub__ @@ -251,9 +248,13 @@ def test_sub_inplace_and_reflected(self) -> None: ires -= self.const_int rres = self.const_int - self.array # type: ignore[operator] - assert res[0].scalar() == ires[0].scalar() == rres[0].scalar() == -1 - assert res[1].scalar() == ires[1].scalar() == rres[1].scalar() == 0 - assert res[2].scalar() == ires[2].scalar() == rres[2].scalar() == 1 + assert res[0].scalar() == ires[0].scalar() == -1 + assert res[1].scalar() == ires[1].scalar() == 0 + assert res[2].scalar() == ires[2].scalar() == 1 + + assert rres[0].scalar() == 1 + assert rres[1].scalar() == 0 + assert rres[2].scalar() == -1 assert res.dtype == ires.dtype == rres.dtype assert res.ndim == ires.ndim == rres.ndim From 1d071be2c8059f36fb656dbc1b16f72c8ad6212d Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 30 Jan 2023 17:11:23 +0200 Subject: [PATCH 17/36] Add required by specification methods --- arrayfire/array_api/_array_object.py | 95 ++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 19 deletions(-) diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/_array_object.py index 6232baca6..41fa9c6f0 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/_array_object.py @@ -2,6 +2,7 @@ import array as py_array import ctypes +import enum from dataclasses import dataclass from typing import Any @@ -125,21 +126,6 @@ def __init__( ctypes.pointer(_cshape.c_array), ctypes.pointer(strides_cshape), dtype.c_api_value, pointer_source.value)) - 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) + _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 _array_as_str(self) - - def __len__(self) -> int: - return self.shape[0] if self.shape else 0 - # Arithmetic Operators def __pos__(self) -> Array: @@ -441,6 +427,36 @@ def __irshift__(self, other: int | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_bitshiftr) + # Methods + + def __abs__(self) -> Array: + # TODO + return NotImplemented + + def __array_namespace__(self, *, api_version: None | str = None) -> Any: + # TODO + return NotImplemented + + def __bool__(self) -> bool: + # TODO + return NotImplemented + + def __complex__(self) -> complex: + # TODO + return NotImplemented + + def __dlpack__(self, *, stream: None | int | Any = None): # type: ignore[no-untyped-def] + # TODO implementation and expected return type -> PyCapsule + return NotImplemented + + def __dlpack_device__(self) -> tuple[enum.Enum, int]: + # TODO + return NotImplemented + + def __float__(self) -> float: + # TODO + return NotImplemented + def __getitem__(self, key: int | slice | tuple[int | slice] | Array, /) -> Array: # TODO: API Specification - key: int | slice | ellipsis | tuple[int | slice] | Array # TODO: refactor @@ -456,6 +472,40 @@ def __getitem__(self, key: int | slice | tuple[int | slice] | Array, /) -> Array ctypes.pointer(out.arr), self.arr, c_dim_t(ndims), _get_indices(key).pointer)) return out + def __index__(self) -> int: + # TODO + return NotImplemented + + def __int__(self) -> int: + # TODO + return NotImplemented + + def __len__(self) -> int: + return self.shape[0] if self.shape else 0 + + def __setitem__( + self, key: int | slice | tuple[int | slice, ...] | Array, value: int | float | bool | Array, /) -> None: + # TODO + return NotImplemented # type: ignore[return-value] # FIXME + + 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) + _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 _array_as_str(self) + + def to_device(self, device: Any, /, *, stream: None | int | Any = None) -> Array: + # TODO implementation and change device type from Any to Device + return NotImplemented + + # Attributes + @property def dtype(self) -> Dtype: out = ctypes.c_int() @@ -464,17 +514,23 @@ def dtype(self) -> Dtype: @property def device(self) -> Any: - raise NotImplementedError + # TODO + return NotImplemented @property def mT(self) -> Array: # TODO - raise NotImplementedError + return NotImplemented @property def T(self) -> Array: - # TODO - raise NotImplementedError + """ + Transpose of the array. + """ + out = Array() + # NOTE conj support is removed because it is never used + safe_call(backend.get().af_transpose(ctypes.pointer(out.arr), self.arr, False)) + return out @property def size(self) -> int: @@ -507,6 +563,7 @@ def scalar(self) -> None | int | float | bool | complex: """ Return the first element of the array """ + # TODO change the logic of this method if self.is_empty(): return None From 04fbb1b27bf1cd7452768b082ee08b1b8e9ef825 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 30 Jan 2023 17:23:24 +0200 Subject: [PATCH 18/36] Change file names --- arrayfire/array_api/__init__.py | 4 ++-- .../{_array_object.py => array_object.py} | 22 +++++++++---------- arrayfire/array_api/{_dtypes.py => dtypes.py} | 0 .../array_api/tests/test_array_object.py | 4 ++-- arrayfire/array_api/{_utils.py => utils.py} | 0 5 files changed, 15 insertions(+), 15 deletions(-) rename arrayfire/array_api/{_array_object.py => array_object.py} (98%) rename arrayfire/array_api/{_dtypes.py => dtypes.py} (100%) rename arrayfire/array_api/{_utils.py => utils.py} (100%) diff --git a/arrayfire/array_api/__init__.py b/arrayfire/array_api/__init__.py index 2de1832ab..675b27ade 100644 --- a/arrayfire/array_api/__init__.py +++ b/arrayfire/array_api/__init__.py @@ -5,5 +5,5 @@ "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float32", "float64", "complex64", "complex128", "bool"] -from ._array_object import Array -from ._dtypes import bool, complex64, complex128, float32, float64, int16, int32, int64, uint8, uint16, uint32, uint64 +from .array_object import Array +from .dtypes import bool, complex64, complex128, float32, float64, int16, int32, int64, uint8, uint16, uint32, uint64 diff --git a/arrayfire/array_api/_array_object.py b/arrayfire/array_api/array_object.py similarity index 98% rename from arrayfire/array_api/_array_object.py rename to arrayfire/array_api/array_object.py index 41fa9c6f0..118fe1d6e 100644 --- a/arrayfire/array_api/_array_object.py +++ b/arrayfire/array_api/array_object.py @@ -10,17 +10,17 @@ from arrayfire.algorithm import count # TODO refactor from arrayfire.array import _get_indices, _in_display_dims_limit # TODO refactor -from ._dtypes import CShape, Dtype -from ._dtypes import bool as af_bool -from ._dtypes import c_dim_t -from ._dtypes import complex64 as af_complex64 -from ._dtypes import complex128 as af_complex128 -from ._dtypes import float32 as af_float32 -from ._dtypes import float64 as af_float64 -from ._dtypes import int64 as af_int64 -from ._dtypes import supported_dtypes -from ._dtypes import uint64 as af_uint64 -from ._utils import PointerSource, to_str +from .dtypes import CShape, Dtype +from .dtypes import bool as af_bool +from .dtypes import c_dim_t +from .dtypes import complex64 as af_complex64 +from .dtypes import complex128 as af_complex128 +from .dtypes import float32 as af_float32 +from .dtypes import float64 as af_float64 +from .dtypes import int64 as af_int64 +from .dtypes import supported_dtypes +from .dtypes import uint64 as af_uint64 +from .utils import PointerSource, to_str ShapeType = tuple[int, ...] _bcast_var = False # HACK, TODO replace for actual bcast_var after refactoring diff --git a/arrayfire/array_api/_dtypes.py b/arrayfire/array_api/dtypes.py similarity index 100% rename from arrayfire/array_api/_dtypes.py rename to arrayfire/array_api/dtypes.py diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index 44d033fd8..579076495 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -3,8 +3,8 @@ import pytest -from arrayfire.array_api import Array, float32, int16 -from arrayfire.array_api._dtypes import supported_dtypes +from arrayfire.array_api.array_object import Array +from arrayfire.array_api.dtypes import float32, int16, supported_dtypes # TODO change separated methods with setup and teardown to avoid code duplication # TODO add tests for array arguments: device, offset, strides diff --git a/arrayfire/array_api/_utils.py b/arrayfire/array_api/utils.py similarity index 100% rename from arrayfire/array_api/_utils.py rename to arrayfire/array_api/utils.py From 2d91b042d64de3797e551ea8902b9716c1cf29c1 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 30 Jan 2023 18:13:54 +0200 Subject: [PATCH 19/36] Change utils. Add docstrings --- arrayfire/array_api/array_object.py | 8 +- arrayfire/array_api/device.py | 10 ++ arrayfire/array_api/dtype_functions.py | 138 +++++++++++++++++++++++++ arrayfire/array_api/utils.py | 82 +++++++++++++-- 4 files changed, 227 insertions(+), 11 deletions(-) create mode 100644 arrayfire/array_api/device.py create mode 100644 arrayfire/array_api/dtype_functions.py diff --git a/arrayfire/array_api/array_object.py b/arrayfire/array_api/array_object.py index 118fe1d6e..2b09b69c6 100644 --- a/arrayfire/array_api/array_object.py +++ b/arrayfire/array_api/array_object.py @@ -10,6 +10,7 @@ from arrayfire.algorithm import count # TODO refactor from arrayfire.array import _get_indices, _in_display_dims_limit # TODO refactor +from .device import PointerSource from .dtypes import CShape, Dtype from .dtypes import bool as af_bool from .dtypes import c_dim_t @@ -20,7 +21,6 @@ from .dtypes import int64 as af_int64 from .dtypes import supported_dtypes from .dtypes import uint64 as af_uint64 -from .utils import PointerSource, to_str ShapeType = tuple[int, ...] _bcast_var = False # HACK, TODO replace for actual bcast_var after refactoring @@ -632,7 +632,7 @@ def _array_as_str(array: Array) -> str: arr_str = ctypes.c_char_p(0) # FIXME add description to passed arguments safe_call(backend.get().af_array_to_string(ctypes.pointer(arr_str), "", array.arr, 4, True)) - py_str = to_str(arr_str) + py_str = _to_str(arr_str) safe_call(backend.get().af_free_host(arr_str)) return py_str @@ -662,6 +662,10 @@ def _c_api_value_to_dtype(value: int) -> Dtype: raise TypeError("There is no supported dtype that matches passed dtype C API value.") +def _to_str(c_str: ctypes.c_char_p) -> str: + return str(c_str.value.decode("utf-8")) # type: ignore[union-attr] + + def _str_to_dtype(value: int) -> Dtype: for dtype in supported_dtypes: if value == dtype.typecode or value == dtype.typename: diff --git a/arrayfire/array_api/device.py b/arrayfire/array_api/device.py new file mode 100644 index 000000000..fde5d6a54 --- /dev/null +++ b/arrayfire/array_api/device.py @@ -0,0 +1,10 @@ +import enum + + +class PointerSource(enum.Enum): + """ + Source of the pointer. + """ + # FIXME + device = 0 + host = 1 diff --git a/arrayfire/array_api/dtype_functions.py b/arrayfire/array_api/dtype_functions.py new file mode 100644 index 000000000..c0a8469c5 --- /dev/null +++ b/arrayfire/array_api/dtype_functions.py @@ -0,0 +1,138 @@ +from .array_object import Array +from .dtypes import Dtype + +# TODO implement functions + + +def astype(x: Array, dtype: Dtype, /, *, copy: bool = True) -> Array: + """ + Copies an array to a specified data type irrespective of Type Promotion Rules rules. + + Parameters + ---------- + x : Array + Array to cast. + dtype: Dtype + Desired data type. + copy: bool, optional + Specifies whether to copy an array when the specified dtype matches the data type of the input array x. + If True, a newly allocated array must always be returned. If False and the specified dtype matches the data + type of the input array, the input array must be returned; otherwise, a newly allocated array must be returned. + Default: True. + + Returns + ------- + out : Array + An array having the specified data type. The returned array must have the same shape as x. + + Note + ---- + - Casting floating-point NaN and infinity values to integral data types is not specified and is + implementation-dependent. + - Casting a complex floating-point array to a real-valued data type should not be permitted. + Historically, when casting a complex floating-point array to a real-valued data type, libraries such as NumPy have + discarded imaginary components such that, for a complex floating-point array x, astype(x) equals astype(real(x))). + This behavior is considered problematic as the choice to discard the imaginary component is arbitrary and + introduces more than one way to achieve the same outcome (i.e., for a complex floating-point array x, astype(x) and + astype(real(x)) versus only astype(imag(x))). Instead, in order to avoid ambiguity and to promote clarity, this + specification requires that array API consumers explicitly express which component should be cast to a specified + real-valued data type. + - When casting a boolean input array to a real-valued data type, a value of True must cast to a real-valued number + equal to 1, and a value of False must cast to a real-valued number equal to 0. + When casting a boolean input array to a complex floating-point data type, a value of True must cast to a complex + number equal to 1 + 0j, and a value of False must cast to a complex number equal to 0 + 0j. + - When casting a real-valued input array to bool, a value of 0 must cast to False, and a non-zero value must cast + to True. + When casting a complex floating-point array to bool, a value of 0 + 0j must cast to False, and all other values + must cast to True. + """ + return NotImplemented + + +def can_cast(from_: Dtype | Array, to: Dtype, /) -> bool: + """ + Determines if one data type can be cast to another data type according Type Promotion Rules rules. + + Parameters + ---------- + from_ : Dtype | Array + Input data type or array from which to cast. + to : Dtype + Desired data type. + + Returns + ------- + out : bool + True if the cast can occur according to Type Promotion Rules rules; otherwise, False. + """ + return NotImplemented + + +def finfo(type: Dtype | Array, /): # type: ignore[no-untyped-def] + # TODO add docstring, implementation and return type -> finfo_object + return NotImplemented + + +def iinfo(type: Dtype | Array, /): # type: ignore[no-untyped-def] + # TODO add docstring, implementation and return type -> iinfo_object + return NotImplemented + + +def isdtype(dtype: Dtype, kind: Dtype | str | tuple[Dtype | str, ...]) -> bool: + """ + Returns a boolean indicating whether a provided dtype is of a specified data type “kind”. + + Parameters + ---------- + dtype : Dtype + The input dtype. + kind : Dtype | str | tuple[Dtype | str, ...] + Data type kind. + - If kind is a dtype, the function must return a boolean indicating whether the input dtype is equal to the + dtype specified by kind. + - If kind is a string, the function must return a boolean indicating whether the input dtype is of a specified + data type kind. The following dtype kinds must be supported: + - bool: boolean data types (e.g., bool). + - signed integer: signed integer data types (e.g., int8, int16, int32, int64). + - unsigned integer: unsigned integer data types (e.g., uint8, uint16, uint32, uint64). + - integral: integer data types. Shorthand for ('signed integer', 'unsigned integer'). + - real floating: real-valued floating-point data types (e.g., float32, float64). + - complex floating: complex floating-point data types (e.g., complex64, complex128). + - numeric: numeric data types. Shorthand for ('integral', 'real floating', 'complex floating'). + - If kind is a tuple, the tuple specifies a union of dtypes and/or kinds, and the function must return a + boolean indicating whether the input dtype is either equal to a specified dtype or belongs to at least one + specified data type kind. + + Returns + ------- + out : bool + Boolean indicating whether a provided dtype is of a specified data type kind. + + Note + ---- + - A conforming implementation of the array API standard is not limited to only including the dtypes described in + this specification in the required data type kinds. For example, implementations supporting float16 and bfloat16 + can include float16 and bfloat16 in the real floating data type kind. Similarly, implementations supporting int128 + can include int128 in the signed integer data type kind. + In short, conforming implementations may extend data type kinds; however, data type kinds must remain consistent + (e.g., only integer dtypes may belong to integer data type kinds and only floating-point dtypes may belong to + floating-point data type kinds), and extensions must be clearly documented as such in library documentation. + """ + return NotImplemented + + +def result_type(*arrays_and_dtypes: Dtype | Array) -> Dtype: + """ + Returns the dtype that results from applying the type promotion rules (see Type Promotion Rules) to the arguments. + + Parameters + ---------- + arrays_and_dtypes: Dtype | Array + An arbitrary number of input arrays and/or dtypes. + + Returns + ------- + out : Dtype + The dtype resulting from an operation involving the input arrays and dtypes. + """ + return NotImplemented diff --git a/arrayfire/array_api/utils.py b/arrayfire/array_api/utils.py index 15a8a2564..f93ab9ea8 100644 --- a/arrayfire/array_api/utils.py +++ b/arrayfire/array_api/utils.py @@ -1,15 +1,79 @@ -import ctypes -import enum +from .array_object import Array +# TODO implement functions -class PointerSource(enum.Enum): + +def all(x: Array, /, *, axis: None | int | tuple[int, ...] = None, keepdims: bool = False) -> Array: """ - Source of the pointer + Tests whether all input array elements evaluate to True along a specified axis. + + Parameters + ---------- + x : Array + Input array. + axis : None | int | tuple[int, ...], optional + Axis or axes along which to perform a logical AND reduction. By default, a logical AND reduction must be + performed over the entire array. If a tuple of integers, logical AND reductions must be performed over + multiple axes. A valid axis must be an integer on the interval [-N, N), where N is the rank (number of + dimensions) of x. If an axis is specified as a negative integer, the function must determine the axis along + which to perform a reduction by counting backward from the last dimension (where -1 refers to the last + dimension). If provided an invalid axis, the function must raise an exception. Default: None. + keepdims : bool, optional + If True, the reduced axes (dimensions) must be included in the result as singleton dimensions, and, + accordingly, the result must be compatible with the input array (see Broadcasting). Otherwise, if False, + the reduced axes (dimensions) must not be included in the result. Default: False. + + Returns + ------- + out : Array + If a logical AND reduction was performed over the entire array, the returned array must be a zero-dimensional + array containing the test result; otherwise, the returned array must be a non-zero-dimensional array + containing the test results. The returned array must have a data type of bool. + + Note + ---- + - Positive infinity, negative infinity, and NaN must evaluate to True. + - If x has a complex floating-point data type, elements having a non-zero component (real or imaginary) must + evaluate to True. + - If x is an empty array or the size of the axis (dimension) along which to evaluate elements is zero, the test + result must be True. """ - # FIXME - device = 0 - host = 1 + return NotImplemented -def to_str(c_str: ctypes.c_char_p) -> str: - return str(c_str.value.decode("utf-8")) # type: ignore[union-attr] +def any(x: Array, /, *, axis: None | int | tuple[int, ...] = None, keepdims: bool = False) -> Array: + """ + Tests whether any input array element evaluates to True along a specified axis. + + Parameters + ---------- + x : Array + Input array. + axis : None | int | tuple[int, ...], optional + Axis or axes along which to perform a logical OR reduction. By default, a logical OR reduction must be + performed over the entire array. If a tuple of integers, logical OR reductions must be performed over + multiple axes. A valid axis must be an integer on the interval [-N, N), where N is the rank (number of + dimensions) of x. If an axis is specified as a negative integer, the function must determine the axis along + which to perform a reduction by counting backward from the last dimension (where -1 refers to the last + dimension). If provided an invalid axis, the function must raise an exception. Default: None. + keepdims : bool, optional + If True, the reduced axes (dimensions) must be included in the result as singleton dimensions, and, + accordingly, the result must be compatible with the input array (see Broadcasting). Otherwise, if False, + the reduced axes (dimensions) must not be included in the result. Default: False. + + Returns + ------- + out : Array + If a logical OR reduction was performed over the entire array, the returned array must be a zero-dimensional + array containing the test result; otherwise, the returned array must be a non-zero-dimensional array + containing the test results. The returned array must have a data type of bool. + + Note + ---- + - Positive infinity, negative infinity, and NaN must evaluate to True. + - If x has a complex floating-point data type, elements having a non-zero component (real or imaginary) must + evaluate to True. + - If x is an empty array or the size of the axis (dimension) along which to evaluate elements is zero, the test + result must be False. + """ + return NotImplemented From 59393880a41221f65129cc3eeaaca966bff52142 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 30 Jan 2023 18:52:04 +0200 Subject: [PATCH 20/36] Add docstrings for operators --- arrayfire/array_api/array_object.py | 238 ++++++++++++++++++++++++++-- 1 file changed, 224 insertions(+), 14 deletions(-) diff --git a/arrayfire/array_api/array_object.py b/arrayfire/array_api/array_object.py index 2b09b69c6..7ecc7a355 100644 --- a/arrayfire/array_api/array_object.py +++ b/arrayfire/array_api/array_object.py @@ -130,38 +130,124 @@ def __init__( def __pos__(self) -> Array: """ - Return +self + Evaluates +self_i for each element of an array instance. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + + Returns + ------- + out : Array + An array containing the evaluated result for each element. The returned array must have the same data type + as self. """ return self def __neg__(self) -> Array: """ - Return -self + Evaluates +self_i for each element of an array instance. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + + Returns + ------- + out : Array + An array containing the evaluated result for each element in self. The returned array must have a data type + determined by Type Promotion Rules. + """ return 0 - self # type: ignore[no-any-return, operator] # FIXME def __add__(self, other: int | float | Array, /) -> Array: # TODO discuss either we need to support complex and bool as other input type """ - Return self + other. + Calculates the sum for each element of an array instance with the respective element of the array other. + + Parameters + ---------- + self : Array + Array instance (augend array). Should have a numeric data type. + other: int | float | Array + Addend array. Must be compatible with self (see Broadcasting). Should have a numeric data type. + + Returns + ------- + out : 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, backend.get().af_add) def __sub__(self, other: int | float | Array, /) -> Array: """ - Return self - other. + Calculates the difference for each element of an array instance with the respective element of the array other. + + The result of self_i - other_i must be the same as self_i + (-other_i) and must be governed by the same + floating-point rules as addition (see array.__add__()). + + Parameters + ---------- + self : Array + Array instance (minuend array). Should have a numeric data type. + other: int | float | Array + Subtrahend array. Must be compatible with self (see Broadcasting). Should have a numeric data type. + + Returns + ------- + out : 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, backend.get().af_sub) def __mul__(self, other: int | float | Array, /) -> Array: """ - Return self * other. + Calculates the product for each element of an array instance with the respective element of the array other. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: int | float | Array + Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. + + Returns + ------- + out : 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, backend.get().af_mul) def __truediv__(self, other: int | float | Array, /) -> Array: """ - Return self / other. + Evaluates self_i / other_i for each element of an array instance with the respective element of the + array other. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: int | float | Array + Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. + + Returns + ------- + out : Array + An array containing the element-wise results. The returned array should have a floating-point data type + determined by Type Promotion Rules. + + Note + ---- + - If one or both of self and other have integer data types, the result is implementation-dependent, as type + promotion between data type “kinds” (e.g., integer versus floating-point) is unspecified. + 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, backend.get().af_div) @@ -171,13 +257,57 @@ def __floordiv__(self, other: int | float | Array, /) -> Array: def __mod__(self, other: int | float | Array, /) -> Array: """ - Return self % other. + Evaluates self_i % other_i for each element of an array instance with the respective element of the + array other. + + Parameters + ---------- + self : Array + Array instance. Should have a real-valued data type. + other: int | float | Array + Other array. Must be compatible with self (see Broadcasting). Should have a real-valued data type. + + Returns + ------- + out : Array + An array containing the element-wise results. Each element-wise result must have the same sign as the + respective element other_i. The returned array must have a real-valued floating-point data type determined + by Type Promotion Rules. + + Note + ---- + - 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, backend.get().af_mod) def __pow__(self, other: int | float | Array, /) -> Array: """ - Return self ** other. + Calculates an implementation-dependent approximation of exponentiation by raising each element (the base) of + an array instance to the power of other_i (the exponent), where other_i is the corresponding element of the + array other. + + Parameters + ---------- + self : Array + Array instance whose elements correspond to the exponentiation base. Should have a numeric data type. + other: int | float | Array + Other array whose elements correspond to the exponentiation exponent. Must be compatible with self + (see Broadcasting). Should have a numeric data type. + + Returns + ------- + out : Array + An array containing the element-wise results. The returned array must have a data type determined + by Type Promotion Rules. + + Note + ---- + - If both self and other have integer data types, the result of __pow__ when other_i is negative + (i.e., less than zero) is unspecified and thus implementation-dependent. + If self has an integer data type and other has a floating-point data type, behavior is + implementation-dependent, as type promotion between data type “kinds” (e.g., integer versus floating-point) + is unspecified. """ return _process_c_function(self, other, backend.get().af_pow) @@ -191,7 +321,17 @@ def __matmul__(self, other: Array, /) -> Array: def __invert__(self) -> Array: """ - Return ~self. + Evaluates ~self_i for each element of an array instance. + + Parameters + ---------- + self : Array + Array instance. Should have an integer or boolean data type. + + Returns + ------- + out : Array + An array containing the element-wise results. The returned array must have the same data type as self. """ out = Array() safe_call(backend.get().af_bitnot(ctypes.pointer(out.arr), self.arr)) @@ -199,31 +339,101 @@ def __invert__(self) -> Array: def __and__(self, other: int | bool | Array, /) -> Array: """ - Return self & other. + Evaluates self_i & other_i for each element of an array instance with the respective element of the + array other. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: int | float | Array + Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. + + Returns + ------- + out : 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, backend.get().af_bitand) def __or__(self, other: int | bool | Array, /) -> Array: """ - Return self | other. + Evaluates self_i | other_i for each element of an array instance with the respective element of the + array other. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: int | float | Array + Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. + + Returns + ------- + out : 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, backend.get().af_bitor) def __xor__(self, other: int | bool | Array, /) -> Array: """ - Return self ^ other. + Evaluates self_i ^ other_i for each element of an array instance with the respective element of the + array other. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: int | float | Array + Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. + + Returns + ------- + out : 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, backend.get().af_bitxor) def __lshift__(self, other: int | Array, /) -> Array: """ - Return self << other. + Evaluates self_i << other_i for each element of an array instance with the respective element of the + array other. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: int | float | Array + Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. + Each element must be greater than or equal to 0. + + Returns + ------- + 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, backend.get().af_bitshiftl) def __rshift__(self, other: int | Array, /) -> Array: """ - Return self >> other. + Evaluates self_i >> other_i for each element of an array instance with the respective element of the + array other. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: int | float | Array + Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. + Each element must be greater than or equal to 0. + + Returns + ------- + 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, backend.get().af_bitshiftr) From 0231e2774002c093f58fd381b533455953408a60 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 30 Jan 2023 19:46:42 +0200 Subject: [PATCH 21/36] Change TODOs --- .github/workflows/build.yaml | 3 ++ arrayfire/array_api/array_object.py | 30 ++++++------------- .../array_api/tests/test_array_object.py | 1 + 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8e420cfbc..c3ca58a39 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -22,5 +22,8 @@ jobs: - name: Test with pytest run: pytest + - name: Install AF + run: apt install arrayfire + - name: Test array_api run: python -m pytest arrayfire/array_api diff --git a/arrayfire/array_api/array_object.py b/arrayfire/array_api/array_object.py index 7ecc7a355..7512c88d3 100644 --- a/arrayfire/array_api/array_object.py +++ b/arrayfire/array_api/array_object.py @@ -23,7 +23,10 @@ from .dtypes import uint64 as af_uint64 ShapeType = tuple[int, ...] -_bcast_var = False # HACK, TODO replace for actual bcast_var after refactoring +# HACK, TODO replace for actual bcast_var after refactoring ~ https://github.com/arrayfire/arrayfire/pull/2871 +_bcast_var = False + +# TODO use int | float in operators -> remove bool | complex support @dataclass @@ -33,12 +36,6 @@ class _ArrayBuffer: class Array: - # Numpy checks this attribute to know which class handles binary builtin operations, such as __add__. - # Setting to such a high value should make sure that arrayfire has priority over - # other classes, ensuring that e.g. numpy.float32(1)*arrayfire.randu(3) is handled by - # arrayfire's __radd__() instead of numpy's __add__() - __array_priority__ = 30 # TODO discuss its purpose - def __init__( self, x: None | Array | py_array.array | int | ctypes.c_void_p | list = None, dtype: None | Dtype | str = None, shape: None | ShapeType = None, @@ -164,7 +161,6 @@ def __neg__(self) -> Array: return 0 - self # type: ignore[no-any-return, operator] # FIXME def __add__(self, other: int | float | Array, /) -> Array: - # TODO discuss either we need to support complex and bool as other input type """ Calculates the sum for each element of an array instance with the respective element of the array other. @@ -300,21 +296,13 @@ def __pow__(self, other: int | float | Array, /) -> Array: out : Array An array containing the element-wise results. The returned array must have a data type determined by Type Promotion Rules. - - Note - ---- - - If both self and other have integer data types, the result of __pow__ when other_i is negative - (i.e., less than zero) is unspecified and thus implementation-dependent. - If self has an integer data type and other has a floating-point data type, behavior is - implementation-dependent, as type promotion between data type “kinds” (e.g., integer versus floating-point) - is unspecified. """ return _process_c_function(self, other, backend.get().af_pow) # Array Operators def __matmul__(self, other: Array, /) -> Array: - # TODO + # TODO get from blas - make vanilla version and not copy af.matmul as is return NotImplemented # Bitwise Operators @@ -508,7 +496,7 @@ def __rfloordiv__(self, other: Array, /) -> Array: def __rmod__(self, other: Array, /) -> Array: """ - Return other / self. + Return other % self. """ return _process_c_function(other, self, backend.get().af_mod) @@ -534,7 +522,7 @@ def __rand__(self, other: Array, /) -> Array: def __ror__(self, other: Array, /) -> Array: """ - Return other & self. + Return other | self. """ return _process_c_function(other, self, backend.get().af_bitor) @@ -648,7 +636,7 @@ def __array_namespace__(self, *, api_version: None | str = None) -> Any: return NotImplemented def __bool__(self) -> bool: - # TODO + # TODO consider using scalar() and is_scalar() return NotImplemented def __complex__(self) -> complex: @@ -668,7 +656,7 @@ def __float__(self) -> float: return NotImplemented def __getitem__(self, key: int | slice | tuple[int | slice] | Array, /) -> Array: - # TODO: API Specification - key: int | slice | ellipsis | tuple[int | slice] | Array + # TODO: API Specification - key: int | slice | ellipsis | tuple[int | slice] | Array - consider using af.span # TODO: refactor out = Array() ndims = self.ndim diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index 579076495..317390929 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -9,6 +9,7 @@ # TODO change separated methods with setup and teardown to avoid code duplication # TODO add tests for array arguments: device, offset, strides # TODO add tests for all supported dtypes on initialisation +# TODO check if e.g. abs(x1-x2) < 1e-6 ~ https://davidamos.dev/the-right-way-to-compare-floats-in-python/ def test_create_empty_array() -> None: From 07c42060aeba5fc0a7bb60e6ea0d53665ede92c8 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 30 Jan 2023 20:53:12 +0200 Subject: [PATCH 22/36] Add docstrings for other operators. Remove docstrings from mocks --- arrayfire/array_api/array_object.py | 170 ++++++++++++++++-- arrayfire/array_api/dtype_functions.py | 112 +----------- .../array_api/tests/test_array_object.py | 9 +- arrayfire/array_api/utils.py | 68 ------- 4 files changed, 161 insertions(+), 198 deletions(-) diff --git a/arrayfire/array_api/array_object.py b/arrayfire/array_api/array_object.py index 7512c88d3..0df29c267 100644 --- a/arrayfire/array_api/array_object.py +++ b/arrayfire/array_api/array_object.py @@ -334,7 +334,7 @@ def __and__(self, other: int | bool | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | float | Array + other: int | bool | Array Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Returns @@ -354,7 +354,7 @@ def __or__(self, other: int | bool | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | float | Array + other: int | bool | Array Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Returns @@ -374,7 +374,7 @@ def __xor__(self, other: int | bool | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | float | Array + other: int | bool | Array Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Returns @@ -394,7 +394,7 @@ def __lshift__(self, other: int | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | float | Array + other: int | Array Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Each element must be greater than or equal to 0. @@ -414,7 +414,7 @@ def __rshift__(self, other: int | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | float | Array + other: int | Array Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Each element must be greater than or equal to 0. @@ -429,44 +429,121 @@ def __rshift__(self, other: int | Array, /) -> Array: def __lt__(self, other: int | float | Array, /) -> Array: """ - Return self < other. + Computes the truth value of self_i < other_i for each element of an array instance with the respective + element of the array other. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: int | float | Array + Other array. Must be compatible with self (see Broadcasting). Should have a real-valued data type. + + Returns + ------- + 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, backend.get().af_lt) def __le__(self, other: int | float | Array, /) -> Array: """ - Return self <= other. + Computes the truth value of self_i <= other_i for each element of an array instance with the respective + element of the array other. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: int | float | Array + Other array. Must be compatible with self (see Broadcasting). Should have a real-valued data type. + + Returns + ------- + 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, backend.get().af_le) def __gt__(self, other: int | float | Array, /) -> Array: """ - Return self > other. + Computes the truth value of self_i > other_i for each element of an array instance with the respective + element of the array other. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: int | float | Array + Other array. Must be compatible with self (see Broadcasting). Should have a real-valued data type. + + Returns + ------- + 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, backend.get().af_gt) def __ge__(self, other: int | float | Array, /) -> Array: """ - Return self >= other. + Computes the truth value of self_i >= other_i for each element of an array instance with the respective + element of the array other. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: int | float | Array + Other array. Must be compatible with self (see Broadcasting). Should have a real-valued data type. + + Returns + ------- + 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, backend.get().af_ge) def __eq__(self, other: int | float | bool | Array, /) -> Array: # type: ignore[override] # FIXME """ - Return self == other. + Computes the truth value of self_i == other_i for each element of an array instance with the respective + element of the array other. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: int | float | bool | Array + Other array. Must be compatible with self (see Broadcasting). May have any data type. + + Returns + ------- + 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, backend.get().af_eq) def __ne__(self, other: int | float | bool | Array, /) -> Array: # type: ignore[override] # FIXME """ - Return self != other. + Computes the truth value of self_i != other_i for each element of an array instance with the respective + element of the array other. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: int | float | bool | Array + Other array. Must be compatible with self (see Broadcasting). May have any data type. + + Returns + ------- + 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, backend.get().af_neq) # Reflected Arithmetic Operators def __radd__(self, other: Array, /) -> Array: - # TODO discuss either we need to support complex and bool as other input type """ Return other + self. """ @@ -656,8 +733,24 @@ def __float__(self) -> float: return NotImplemented def __getitem__(self, key: int | slice | tuple[int | slice] | Array, /) -> Array: - # TODO: API Specification - key: int | slice | ellipsis | tuple[int | slice] | Array - consider using af.span - # TODO: refactor + """ + Returns self[key]. + + Parameters + ---------- + self : Array + Array instance. + key : int | slice | tuple[int | slice] | Array + Index key. + + Returns + ------- + out : Array + An array containing the accessed value(s). The returned array must have the same data type as self. + """ + # TODO + # API Specification - key: int | slice | ellipsis | tuple[int | slice] | Array. + # consider using af.span to replace ellipsis during refactoring out = Array() ndims = self.ndim @@ -706,6 +799,14 @@ def to_device(self, device: Any, /, *, stream: None | int | Any = None) -> Array @property def dtype(self) -> Dtype: + """ + Data type of the array elements. + + Returns + ------- + out : Dtype + Array data type. + """ out = ctypes.c_int() safe_call(backend.get().af_get_type(ctypes.pointer(out), self.arr)) return _c_api_value_to_dtype(out.value) @@ -724,14 +825,40 @@ def mT(self) -> Array: def T(self) -> Array: """ Transpose of the array. + + Returns + ------- + out : Array + Two-dimensional array whose first and last dimensions (axes) are permuted in reverse order relative to + original array. The returned array must have the same data type as the original array. + + Note + ---- + - The array instance must be two-dimensional. If the array instance is not two-dimensional, an error + should be raised. """ + if self.ndim < 2: + raise TypeError(f"Array should be at least 2-dimensional. Got {self.ndim}-dimensional array") + + # TODO add check if out.dtype == self.dtype out = Array() - # NOTE conj support is removed because it is never used safe_call(backend.get().af_transpose(ctypes.pointer(out.arr), self.arr, False)) return out @property def size(self) -> int: + """ + Number of elements in an array. + + Returns + ------- + out : int + Number of elements in an array + + Note + ---- + - This must equal the product of the array's dimensions. + """ # NOTE previously - elements() out = c_dim_t(0) safe_call(backend.get().af_get_elements(ctypes.pointer(out), self.arr)) @@ -739,6 +866,12 @@ def size(self) -> int: @property def ndim(self) -> int: + """ + Number of array dimensions (axes). + + out : int + Number of array dimensions (axes). + """ out = ctypes.c_uint(0) safe_call(backend.get().af_get_numdims(ctypes.pointer(out), self.arr)) return out.value @@ -746,7 +879,12 @@ def ndim(self) -> int: @property def shape(self) -> ShapeType: """ - Return the shape of the array as a tuple. + Array dimensions. + + Returns + ------- + out : tuple[int, ...] + Array dimensions. """ # TODO refactor d0 = c_dim_t(0) diff --git a/arrayfire/array_api/dtype_functions.py b/arrayfire/array_api/dtype_functions.py index c0a8469c5..905d1ba97 100644 --- a/arrayfire/array_api/dtype_functions.py +++ b/arrayfire/array_api/dtype_functions.py @@ -5,134 +5,26 @@ def astype(x: Array, dtype: Dtype, /, *, copy: bool = True) -> Array: - """ - Copies an array to a specified data type irrespective of Type Promotion Rules rules. - - Parameters - ---------- - x : Array - Array to cast. - dtype: Dtype - Desired data type. - copy: bool, optional - Specifies whether to copy an array when the specified dtype matches the data type of the input array x. - If True, a newly allocated array must always be returned. If False and the specified dtype matches the data - type of the input array, the input array must be returned; otherwise, a newly allocated array must be returned. - Default: True. - - Returns - ------- - out : Array - An array having the specified data type. The returned array must have the same shape as x. - - Note - ---- - - Casting floating-point NaN and infinity values to integral data types is not specified and is - implementation-dependent. - - Casting a complex floating-point array to a real-valued data type should not be permitted. - Historically, when casting a complex floating-point array to a real-valued data type, libraries such as NumPy have - discarded imaginary components such that, for a complex floating-point array x, astype(x) equals astype(real(x))). - This behavior is considered problematic as the choice to discard the imaginary component is arbitrary and - introduces more than one way to achieve the same outcome (i.e., for a complex floating-point array x, astype(x) and - astype(real(x)) versus only astype(imag(x))). Instead, in order to avoid ambiguity and to promote clarity, this - specification requires that array API consumers explicitly express which component should be cast to a specified - real-valued data type. - - When casting a boolean input array to a real-valued data type, a value of True must cast to a real-valued number - equal to 1, and a value of False must cast to a real-valued number equal to 0. - When casting a boolean input array to a complex floating-point data type, a value of True must cast to a complex - number equal to 1 + 0j, and a value of False must cast to a complex number equal to 0 + 0j. - - When casting a real-valued input array to bool, a value of 0 must cast to False, and a non-zero value must cast - to True. - When casting a complex floating-point array to bool, a value of 0 + 0j must cast to False, and all other values - must cast to True. - """ return NotImplemented def can_cast(from_: Dtype | Array, to: Dtype, /) -> bool: - """ - Determines if one data type can be cast to another data type according Type Promotion Rules rules. - - Parameters - ---------- - from_ : Dtype | Array - Input data type or array from which to cast. - to : Dtype - Desired data type. - - Returns - ------- - out : bool - True if the cast can occur according to Type Promotion Rules rules; otherwise, False. - """ return NotImplemented def finfo(type: Dtype | Array, /): # type: ignore[no-untyped-def] - # TODO add docstring, implementation and return type -> finfo_object + # NOTE expected return type -> finfo_object return NotImplemented def iinfo(type: Dtype | Array, /): # type: ignore[no-untyped-def] - # TODO add docstring, implementation and return type -> iinfo_object + # NOTE expected return type -> iinfo_object return NotImplemented def isdtype(dtype: Dtype, kind: Dtype | str | tuple[Dtype | str, ...]) -> bool: - """ - Returns a boolean indicating whether a provided dtype is of a specified data type “kind”. - - Parameters - ---------- - dtype : Dtype - The input dtype. - kind : Dtype | str | tuple[Dtype | str, ...] - Data type kind. - - If kind is a dtype, the function must return a boolean indicating whether the input dtype is equal to the - dtype specified by kind. - - If kind is a string, the function must return a boolean indicating whether the input dtype is of a specified - data type kind. The following dtype kinds must be supported: - - bool: boolean data types (e.g., bool). - - signed integer: signed integer data types (e.g., int8, int16, int32, int64). - - unsigned integer: unsigned integer data types (e.g., uint8, uint16, uint32, uint64). - - integral: integer data types. Shorthand for ('signed integer', 'unsigned integer'). - - real floating: real-valued floating-point data types (e.g., float32, float64). - - complex floating: complex floating-point data types (e.g., complex64, complex128). - - numeric: numeric data types. Shorthand for ('integral', 'real floating', 'complex floating'). - - If kind is a tuple, the tuple specifies a union of dtypes and/or kinds, and the function must return a - boolean indicating whether the input dtype is either equal to a specified dtype or belongs to at least one - specified data type kind. - - Returns - ------- - out : bool - Boolean indicating whether a provided dtype is of a specified data type kind. - - Note - ---- - - A conforming implementation of the array API standard is not limited to only including the dtypes described in - this specification in the required data type kinds. For example, implementations supporting float16 and bfloat16 - can include float16 and bfloat16 in the real floating data type kind. Similarly, implementations supporting int128 - can include int128 in the signed integer data type kind. - In short, conforming implementations may extend data type kinds; however, data type kinds must remain consistent - (e.g., only integer dtypes may belong to integer data type kinds and only floating-point dtypes may belong to - floating-point data type kinds), and extensions must be clearly documented as such in library documentation. - """ return NotImplemented def result_type(*arrays_and_dtypes: Dtype | Array) -> Dtype: - """ - Returns the dtype that results from applying the type promotion rules (see Type Promotion Rules) to the arguments. - - Parameters - ---------- - arrays_and_dtypes: Dtype | Array - An arbitrary number of input arrays and/or dtypes. - - Returns - ------- - out : Dtype - The dtype resulting from an operation involving the input arrays and dtypes. - """ return NotImplemented diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index 317390929..f30567553 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -1,4 +1,5 @@ import array as pyarray +import math from typing import Any import pytest @@ -62,7 +63,7 @@ def test_create_empty_array_with_nonempty_shape() -> None: assert array.dtype == float32 assert array.ndim == 2 - assert array.size == 6 + assert array.size == math.prod(array.shape) == 6 assert array.shape == (2, 3) assert len(array) == 2 @@ -72,7 +73,7 @@ def test_create_array_from_1d_list() -> None: assert array.dtype == float32 assert array.ndim == 1 - assert array.size == 3 + assert array.size == math.prod(array.shape) == 3 assert array.shape == (3,) assert len(array) == 3 @@ -88,7 +89,7 @@ def test_create_array_from_pyarray() -> None: assert array.dtype == float32 assert array.ndim == 1 - assert array.size == 3 + assert array.size == math.prod(array.shape) == 3 assert array.shape == (3,) assert len(array) == 3 @@ -107,7 +108,7 @@ def test_array_from_af_array() -> None: assert array1.dtype == array2.dtype == float32 assert array1.ndim == array2.ndim == 1 - assert array1.size == array2.size == 1 + assert array1.size == array2.size == math.prod(array1.shape) == math.prod(array2.shape) == 1 assert array1.shape == array2.shape == (1,) assert len(array1) == len(array2) == 1 diff --git a/arrayfire/array_api/utils.py b/arrayfire/array_api/utils.py index f93ab9ea8..779459efb 100644 --- a/arrayfire/array_api/utils.py +++ b/arrayfire/array_api/utils.py @@ -4,76 +4,8 @@ def all(x: Array, /, *, axis: None | int | tuple[int, ...] = None, keepdims: bool = False) -> Array: - """ - Tests whether all input array elements evaluate to True along a specified axis. - - Parameters - ---------- - x : Array - Input array. - axis : None | int | tuple[int, ...], optional - Axis or axes along which to perform a logical AND reduction. By default, a logical AND reduction must be - performed over the entire array. If a tuple of integers, logical AND reductions must be performed over - multiple axes. A valid axis must be an integer on the interval [-N, N), where N is the rank (number of - dimensions) of x. If an axis is specified as a negative integer, the function must determine the axis along - which to perform a reduction by counting backward from the last dimension (where -1 refers to the last - dimension). If provided an invalid axis, the function must raise an exception. Default: None. - keepdims : bool, optional - If True, the reduced axes (dimensions) must be included in the result as singleton dimensions, and, - accordingly, the result must be compatible with the input array (see Broadcasting). Otherwise, if False, - the reduced axes (dimensions) must not be included in the result. Default: False. - - Returns - ------- - out : Array - If a logical AND reduction was performed over the entire array, the returned array must be a zero-dimensional - array containing the test result; otherwise, the returned array must be a non-zero-dimensional array - containing the test results. The returned array must have a data type of bool. - - Note - ---- - - Positive infinity, negative infinity, and NaN must evaluate to True. - - If x has a complex floating-point data type, elements having a non-zero component (real or imaginary) must - evaluate to True. - - If x is an empty array or the size of the axis (dimension) along which to evaluate elements is zero, the test - result must be True. - """ return NotImplemented def any(x: Array, /, *, axis: None | int | tuple[int, ...] = None, keepdims: bool = False) -> Array: - """ - Tests whether any input array element evaluates to True along a specified axis. - - Parameters - ---------- - x : Array - Input array. - axis : None | int | tuple[int, ...], optional - Axis or axes along which to perform a logical OR reduction. By default, a logical OR reduction must be - performed over the entire array. If a tuple of integers, logical OR reductions must be performed over - multiple axes. A valid axis must be an integer on the interval [-N, N), where N is the rank (number of - dimensions) of x. If an axis is specified as a negative integer, the function must determine the axis along - which to perform a reduction by counting backward from the last dimension (where -1 refers to the last - dimension). If provided an invalid axis, the function must raise an exception. Default: None. - keepdims : bool, optional - If True, the reduced axes (dimensions) must be included in the result as singleton dimensions, and, - accordingly, the result must be compatible with the input array (see Broadcasting). Otherwise, if False, - the reduced axes (dimensions) must not be included in the result. Default: False. - - Returns - ------- - out : Array - If a logical OR reduction was performed over the entire array, the returned array must be a zero-dimensional - array containing the test result; otherwise, the returned array must be a non-zero-dimensional array - containing the test results. The returned array must have a data type of bool. - - Note - ---- - - Positive infinity, negative infinity, and NaN must evaluate to True. - - If x has a complex floating-point data type, elements having a non-zero component (real or imaginary) must - evaluate to True. - - If x is an empty array or the size of the axis (dimension) along which to evaluate elements is zero, the test - result must be False. - """ return NotImplemented From 908447bea4e0c5bca88233841cf350112514112a Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 5 Feb 2023 00:22:03 +0200 Subject: [PATCH 23/36] Change tags and typings --- arrayfire/array_api/array_object.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/arrayfire/array_api/array_object.py b/arrayfire/array_api/array_object.py index 0df29c267..294f2e451 100644 --- a/arrayfire/array_api/array_object.py +++ b/arrayfire/array_api/array_object.py @@ -6,9 +6,10 @@ from dataclasses import dataclass from typing import Any -from arrayfire import backend, safe_call # TODO refactor -from arrayfire.algorithm import count # TODO refactor -from arrayfire.array import _get_indices, _in_display_dims_limit # TODO refactor +# TODO replace imports from original lib with refactored ones +from arrayfire import backend, safe_call +from arrayfire.algorithm import count +from arrayfire.array import _get_indices, _in_display_dims_limit from .device import PointerSource from .dtypes import CShape, Dtype @@ -630,35 +631,35 @@ def __iadd__(self, other: int | float | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_add) - def __isub__(self, other: int | float | bool | complex | Array, /) -> Array: + def __isub__(self, other: int | float | Array, /) -> Array: """ Return self -= other. """ return _process_c_function(self, other, backend.get().af_sub) - def __imul__(self, other: int | float | bool | complex | Array, /) -> Array: + def __imul__(self, other: int | float | Array, /) -> Array: """ Return self *= other. """ return _process_c_function(self, other, backend.get().af_mul) - def __itruediv__(self, other: int | float | bool | complex | Array, /) -> Array: + def __itruediv__(self, other: int | float | Array, /) -> Array: """ Return self /= other. """ return _process_c_function(self, other, backend.get().af_div) - def __ifloordiv__(self, other: int | float | bool | complex | Array, /) -> Array: + def __ifloordiv__(self, other: int | float | Array, /) -> Array: # TODO return NotImplemented - def __imod__(self, other: int | float | bool | complex | Array, /) -> Array: + def __imod__(self, other: int | float | Array, /) -> Array: """ Return self %= other. """ return _process_c_function(self, other, backend.get().af_mod) - def __ipow__(self, other: int | float | bool | complex | Array, /) -> Array: + def __ipow__(self, other: int | float | Array, /) -> Array: """ Return self **= other. """ @@ -1078,10 +1079,12 @@ def _constant_array(value: int | float | bool | complex, shape: CShape, dtype: D elif dtype == af_int64: # TODO discuss workaround for passing float to ctypes safe_call(backend.get().af_constant_long( - ctypes.pointer(out.arr), ctypes.c_longlong(value.real), 4, ctypes.pointer(shape.c_array))) + ctypes.pointer(out.arr), ctypes.c_longlong(value.real), # type: ignore[arg-type] + 4, ctypes.pointer(shape.c_array))) elif dtype == af_uint64: safe_call(backend.get().af_constant_ulong( - ctypes.pointer(out.arr), ctypes.c_ulonglong(value.real), 4, ctypes.pointer(shape.c_array))) + ctypes.pointer(out.arr), ctypes.c_ulonglong(value.real), # type: ignore[arg-type] + 4, ctypes.pointer(shape.c_array))) else: safe_call(backend.get().af_constant( ctypes.pointer(out.arr), ctypes.c_double(value), 4, ctypes.pointer(shape.c_array), dtype.c_api_value)) From fa3ad061195927eb2e562983780bbda359d8b9aa Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 5 Feb 2023 00:41:18 +0200 Subject: [PATCH 24/36] Change typings from python 3.10 to python 3.8 --- arrayfire/array_api/array_object.py | 143 ++++++++++++++-------------- arrayfire/array_api/dtypes.py | 14 --- 2 files changed, 72 insertions(+), 85 deletions(-) diff --git a/arrayfire/array_api/array_object.py b/arrayfire/array_api/array_object.py index 294f2e451..30977895f 100644 --- a/arrayfire/array_api/array_object.py +++ b/arrayfire/array_api/array_object.py @@ -4,7 +4,7 @@ import ctypes import enum from dataclasses import dataclass -from typing import Any +from typing import Any, List, Optional, Tuple, Union # TODO replace imports from original lib with refactored ones from arrayfire import backend, safe_call @@ -23,7 +23,7 @@ from .dtypes import supported_dtypes from .dtypes import uint64 as af_uint64 -ShapeType = tuple[int, ...] +ShapeType = Tuple[int, ...] # HACK, TODO replace for actual bcast_var after refactoring ~ https://github.com/arrayfire/arrayfire/pull/2871 _bcast_var = False @@ -32,16 +32,16 @@ @dataclass class _ArrayBuffer: - address: int | None = None + address: Optional[int] = None length: int = 0 class Array: def __init__( - self, x: None | Array | py_array.array | int | ctypes.c_void_p | list = None, - dtype: None | Dtype | str = None, shape: None | ShapeType = None, - pointer_source: PointerSource = PointerSource.host, offset: None | ctypes._SimpleCData[int] = None, - strides: None | ShapeType = None) -> None: + self, x: Union[None, Array, py_array.array, int, ctypes.c_void_p, List[Union[int, float]]] = None, + dtype: Union[None, Dtype, str] = None, shape: Optional[ShapeType] = None, + pointer_source: PointerSource = PointerSource.host, offset: Optional[ctypes._SimpleCData[int]] = None, + strides: Optional[ShapeType] = None) -> None: _no_initial_dtype = False # HACK, FIXME # Initialise array object @@ -161,7 +161,7 @@ def __neg__(self) -> Array: """ return 0 - self # type: ignore[no-any-return, operator] # FIXME - def __add__(self, other: int | float | Array, /) -> Array: + def __add__(self, other: Union[int, float, Array], /) -> Array: """ Calculates the sum for each element of an array instance with the respective element of the array other. @@ -169,7 +169,7 @@ def __add__(self, other: int | float | Array, /) -> Array: ---------- self : Array Array instance (augend array). Should have a numeric data type. - other: int | float | Array + other: Union[int, float, Array] Addend array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Returns @@ -180,7 +180,7 @@ def __add__(self, other: int | float | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_add) - def __sub__(self, other: int | float | Array, /) -> Array: + def __sub__(self, other: Union[int, float, Array], /) -> Array: """ Calculates the difference for each element of an array instance with the respective element of the array other. @@ -191,7 +191,7 @@ def __sub__(self, other: int | float | Array, /) -> Array: ---------- self : Array Array instance (minuend array). Should have a numeric data type. - other: int | float | Array + other: Union[int, float, Array] Subtrahend array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Returns @@ -202,7 +202,7 @@ def __sub__(self, other: int | float | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_sub) - def __mul__(self, other: int | float | Array, /) -> Array: + def __mul__(self, other: Union[int, float, Array], /) -> Array: """ Calculates the product for each element of an array instance with the respective element of the array other. @@ -210,7 +210,7 @@ def __mul__(self, other: int | float | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | float | Array + other: Union[int, float, Array] Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Returns @@ -221,7 +221,7 @@ def __mul__(self, other: int | float | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_mul) - def __truediv__(self, other: int | float | Array, /) -> Array: + def __truediv__(self, other: Union[int, float, Array], /) -> Array: """ Evaluates self_i / other_i for each element of an array instance with the respective element of the array other. @@ -230,7 +230,7 @@ def __truediv__(self, other: int | float | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | float | Array + other: Union[int, float, Array] Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Returns @@ -248,11 +248,11 @@ def __truediv__(self, other: int | float | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_div) - def __floordiv__(self, other: int | float | Array, /) -> Array: + def __floordiv__(self, other: Union[int, float, Array], /) -> Array: # TODO return NotImplemented - def __mod__(self, other: int | float | Array, /) -> Array: + def __mod__(self, other: Union[int, float, Array], /) -> Array: """ Evaluates self_i % other_i for each element of an array instance with the respective element of the array other. @@ -261,7 +261,7 @@ def __mod__(self, other: int | float | Array, /) -> Array: ---------- self : Array Array instance. Should have a real-valued data type. - other: int | float | Array + other: Union[int, float, Array] Other array. Must be compatible with self (see Broadcasting). Should have a real-valued data type. Returns @@ -278,7 +278,7 @@ def __mod__(self, other: int | float | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_mod) - def __pow__(self, other: int | float | Array, /) -> Array: + def __pow__(self, other: Union[int, float, Array], /) -> Array: """ Calculates an implementation-dependent approximation of exponentiation by raising each element (the base) of an array instance to the power of other_i (the exponent), where other_i is the corresponding element of the @@ -288,7 +288,7 @@ def __pow__(self, other: int | float | Array, /) -> Array: ---------- self : Array Array instance whose elements correspond to the exponentiation base. Should have a numeric data type. - other: int | float | Array + other: Union[int, float, Array] Other array whose elements correspond to the exponentiation exponent. Must be compatible with self (see Broadcasting). Should have a numeric data type. @@ -326,7 +326,7 @@ def __invert__(self) -> Array: safe_call(backend.get().af_bitnot(ctypes.pointer(out.arr), self.arr)) return out - def __and__(self, other: int | bool | Array, /) -> Array: + def __and__(self, other: Union[int, bool, Array], /) -> Array: """ Evaluates self_i & other_i for each element of an array instance with the respective element of the array other. @@ -335,7 +335,7 @@ def __and__(self, other: int | bool | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | bool | Array + other: Union[int, bool, Array] Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Returns @@ -346,7 +346,7 @@ def __and__(self, other: int | bool | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_bitand) - def __or__(self, other: int | bool | Array, /) -> Array: + def __or__(self, other: Union[int, bool, Array], /) -> Array: """ Evaluates self_i | other_i for each element of an array instance with the respective element of the array other. @@ -355,7 +355,7 @@ def __or__(self, other: int | bool | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | bool | Array + other: Union[int, bool, Array] Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Returns @@ -366,7 +366,7 @@ def __or__(self, other: int | bool | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_bitor) - def __xor__(self, other: int | bool | Array, /) -> Array: + def __xor__(self, other: Union[int, bool, Array], /) -> Array: """ Evaluates self_i ^ other_i for each element of an array instance with the respective element of the array other. @@ -375,7 +375,7 @@ def __xor__(self, other: int | bool | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | bool | Array + other: Union[int, bool, Array] Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Returns @@ -386,7 +386,7 @@ def __xor__(self, other: int | bool | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_bitxor) - def __lshift__(self, other: int | Array, /) -> Array: + def __lshift__(self, other: Union[int, Array], /) -> Array: """ Evaluates self_i << other_i for each element of an array instance with the respective element of the array other. @@ -395,7 +395,7 @@ def __lshift__(self, other: int | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | Array + other: Union[int, Array] Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Each element must be greater than or equal to 0. @@ -406,7 +406,7 @@ def __lshift__(self, other: int | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_bitshiftl) - def __rshift__(self, other: int | Array, /) -> Array: + def __rshift__(self, other: Union[int, Array], /) -> Array: """ Evaluates self_i >> other_i for each element of an array instance with the respective element of the array other. @@ -415,7 +415,7 @@ def __rshift__(self, other: int | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | Array + other: Union[int, Array] Other array. Must be compatible with self (see Broadcasting). Should have a numeric data type. Each element must be greater than or equal to 0. @@ -428,7 +428,7 @@ def __rshift__(self, other: int | Array, /) -> Array: # Comparison Operators - def __lt__(self, other: int | float | Array, /) -> Array: + def __lt__(self, other: Union[int, float, Array], /) -> Array: """ Computes the truth value of self_i < other_i for each element of an array instance with the respective element of the array other. @@ -437,7 +437,7 @@ def __lt__(self, other: int | float | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | float | Array + other: Union[int, float, Array] Other array. Must be compatible with self (see Broadcasting). Should have a real-valued data type. Returns @@ -447,7 +447,7 @@ def __lt__(self, other: int | float | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_lt) - def __le__(self, other: int | float | Array, /) -> Array: + def __le__(self, other: Union[int, float, Array], /) -> Array: """ Computes the truth value of self_i <= other_i for each element of an array instance with the respective element of the array other. @@ -456,7 +456,7 @@ def __le__(self, other: int | float | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | float | Array + other: Union[int, float, Array] Other array. Must be compatible with self (see Broadcasting). Should have a real-valued data type. Returns @@ -466,7 +466,7 @@ def __le__(self, other: int | float | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_le) - def __gt__(self, other: int | float | Array, /) -> Array: + def __gt__(self, other: Union[int, float, Array], /) -> Array: """ Computes the truth value of self_i > other_i for each element of an array instance with the respective element of the array other. @@ -475,7 +475,7 @@ def __gt__(self, other: int | float | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | float | Array + other: Union[int, float, Array] Other array. Must be compatible with self (see Broadcasting). Should have a real-valued data type. Returns @@ -485,7 +485,7 @@ def __gt__(self, other: int | float | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_gt) - def __ge__(self, other: int | float | Array, /) -> Array: + def __ge__(self, other: Union[int, float, Array], /) -> Array: """ Computes the truth value of self_i >= other_i for each element of an array instance with the respective element of the array other. @@ -494,7 +494,7 @@ def __ge__(self, other: int | float | Array, /) -> Array: ---------- self : Array Array instance. Should have a numeric data type. - other: int | float | Array + other: Union[int, float, Array] Other array. Must be compatible with self (see Broadcasting). Should have a real-valued data type. Returns @@ -504,7 +504,7 @@ def __ge__(self, other: int | float | Array, /) -> Array: """ return _process_c_function(self, other, backend.get().af_ge) - def __eq__(self, other: int | float | bool | Array, /) -> Array: # type: ignore[override] # FIXME + def __eq__(self, other: Union[int, float, bool, Array], /) -> Array: # type: ignore[override] # FIXME """ Computes the truth value of self_i == other_i for each element of an array instance with the respective element of the array other. @@ -513,7 +513,7 @@ def __eq__(self, other: int | float | bool | Array, /) -> Array: # type: ignore ---------- self : Array Array instance. Should have a numeric data type. - other: int | float | bool | Array + other: Union[int, float, bool, Array] Other array. Must be compatible with self (see Broadcasting). May have any data type. Returns @@ -523,7 +523,7 @@ def __eq__(self, other: int | float | bool | Array, /) -> Array: # type: ignore """ return _process_c_function(self, other, backend.get().af_eq) - def __ne__(self, other: int | float | bool | Array, /) -> Array: # type: ignore[override] # FIXME + def __ne__(self, other: Union[int, float, bool, Array], /) -> Array: # type: ignore[override] # FIXME """ Computes the truth value of self_i != other_i for each element of an array instance with the respective element of the array other. @@ -532,7 +532,7 @@ def __ne__(self, other: int | float | bool | Array, /) -> Array: # type: ignore ---------- self : Array Array instance. Should have a numeric data type. - other: int | float | bool | Array + other: Union[int, float, bool, Array] Other array. Must be compatible with self (see Broadcasting). May have any data type. Returns @@ -624,42 +624,42 @@ def __rrshift__(self, other: Array, /) -> Array: # In-place Arithmetic Operators - def __iadd__(self, other: int | float | Array, /) -> Array: + def __iadd__(self, other: Union[int, float, Array], /) -> Array: # TODO discuss either we need to support complex and bool as other input type """ Return self += other. """ return _process_c_function(self, other, backend.get().af_add) - def __isub__(self, other: int | float | Array, /) -> Array: + def __isub__(self, other: Union[int, float, Array], /) -> Array: """ Return self -= other. """ return _process_c_function(self, other, backend.get().af_sub) - def __imul__(self, other: int | float | Array, /) -> Array: + def __imul__(self, other: Union[int, float, Array], /) -> Array: """ Return self *= other. """ return _process_c_function(self, other, backend.get().af_mul) - def __itruediv__(self, other: int | float | Array, /) -> Array: + def __itruediv__(self, other: Union[int, float, Array], /) -> Array: """ Return self /= other. """ return _process_c_function(self, other, backend.get().af_div) - def __ifloordiv__(self, other: int | float | Array, /) -> Array: + def __ifloordiv__(self, other: Union[int, float, Array], /) -> Array: # TODO return NotImplemented - def __imod__(self, other: int | float | Array, /) -> Array: + def __imod__(self, other: Union[int, float, Array], /) -> Array: """ Return self %= other. """ return _process_c_function(self, other, backend.get().af_mod) - def __ipow__(self, other: int | float | Array, /) -> Array: + def __ipow__(self, other: Union[int, float, Array], /) -> Array: """ Return self **= other. """ @@ -673,31 +673,31 @@ def __imatmul__(self, other: Array, /) -> Array: # In-place Bitwise Operators - def __iand__(self, other: int | bool | Array, /) -> Array: + def __iand__(self, other: Union[int, bool, Array], /) -> Array: """ Return self &= other. """ return _process_c_function(self, other, backend.get().af_bitand) - def __ior__(self, other: int | bool | Array, /) -> Array: + def __ior__(self, other: Union[int, bool, Array], /) -> Array: """ Return self |= other. """ return _process_c_function(self, other, backend.get().af_bitor) - def __ixor__(self, other: int | bool | Array, /) -> Array: + def __ixor__(self, other: Union[int, bool, Array], /) -> Array: """ Return self ^= other. """ return _process_c_function(self, other, backend.get().af_bitxor) - def __ilshift__(self, other: int | Array, /) -> Array: + def __ilshift__(self, other: Union[int, Array], /) -> Array: """ Return self <<= other. """ return _process_c_function(self, other, backend.get().af_bitshiftl) - def __irshift__(self, other: int | Array, /) -> Array: + def __irshift__(self, other: Union[int, Array], /) -> Array: """ Return self >>= other. """ @@ -709,7 +709,7 @@ def __abs__(self) -> Array: # TODO return NotImplemented - def __array_namespace__(self, *, api_version: None | str = None) -> Any: + def __array_namespace__(self, *, api_version: Optional[str] = None) -> Any: # TODO return NotImplemented @@ -721,11 +721,11 @@ def __complex__(self) -> complex: # TODO return NotImplemented - def __dlpack__(self, *, stream: None | int | Any = None): # type: ignore[no-untyped-def] + def __dlpack__(self, *, stream: Union[None, int, Any] = None): # type: ignore[no-untyped-def] # TODO implementation and expected return type -> PyCapsule return NotImplemented - def __dlpack_device__(self) -> tuple[enum.Enum, int]: + def __dlpack_device__(self) -> Tuple[enum.Enum, int]: # TODO return NotImplemented @@ -733,7 +733,7 @@ def __float__(self) -> float: # TODO return NotImplemented - def __getitem__(self, key: int | slice | tuple[int | slice] | Array, /) -> Array: + def __getitem__(self, key: Union[int, slice, Tuple[Union[int, slice, ], ...], Array], /) -> Array: """ Returns self[key]. @@ -741,7 +741,7 @@ def __getitem__(self, key: int | slice | tuple[int | slice] | Array, /) -> Array ---------- self : Array Array instance. - key : int | slice | tuple[int | slice] | Array + key : Union[int, slice, Tuple[Union[int, slice, ], ...], Array] Index key. Returns @@ -750,7 +750,7 @@ def __getitem__(self, key: int | slice | tuple[int | slice] | Array, /) -> Array An array containing the accessed value(s). The returned array must have the same data type as self. """ # TODO - # API Specification - key: int | slice | ellipsis | tuple[int | slice] | Array. + # API Specification - key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], array]. # consider using af.span to replace ellipsis during refactoring out = Array() ndims = self.ndim @@ -776,7 +776,8 @@ def __len__(self) -> int: return self.shape[0] if self.shape else 0 def __setitem__( - self, key: int | slice | tuple[int | slice, ...] | Array, value: int | float | bool | Array, /) -> None: + self, key: Union[int, slice, Tuple[Union[int, slice, ], ...], Array], + value: Union[int, float, bool, Array], /) -> None: # TODO return NotImplemented # type: ignore[return-value] # FIXME @@ -792,7 +793,7 @@ def __repr__(self) -> str: # TODO change the look of array representation. E.g., like np.array return _array_as_str(self) - def to_device(self, device: Any, /, *, stream: None | int | Any = None) -> Array: + def to_device(self, device: Any, /, *, stream: Union[int, Any] = None) -> Array: # TODO implementation and change device type from Any to Device return NotImplemented @@ -896,7 +897,7 @@ def shape(self) -> ShapeType: ctypes.pointer(d0), ctypes.pointer(d1), ctypes.pointer(d2), ctypes.pointer(d3), self.arr)) return (d0.value, d1.value, d2.value, d3.value)[:self.ndim] # Skip passing None values - def scalar(self) -> None | int | float | bool | complex: + def scalar(self) -> Union[None, int, float, bool, complex]: """ Return the first element of the array """ @@ -916,7 +917,7 @@ def is_empty(self) -> bool: safe_call(backend.get().af_is_empty(ctypes.pointer(out), self.arr)) return out.value - def to_list(self, row_major: bool = False) -> list: # FIXME return typings + def to_list(self, row_major: bool = False) -> List[Union[None, int, float, bool, complex]]: if self.is_empty(): return [] @@ -974,14 +975,14 @@ def _array_as_str(array: Array) -> str: return py_str -def _metadata_string(dtype: Dtype, dims: None | ShapeType = None) -> str: +def _metadata_string(dtype: Dtype, dims: Optional[ShapeType] = None) -> str: return ( "arrayfire.Array()\n" f"Type: {dtype.typename}\n" f"Dims: {str(dims) if dims else ''}") -def _get_cshape(shape: None | ShapeType, buffer_length: int) -> CShape: +def _get_cshape(shape: Optional[ShapeType], buffer_length: int) -> CShape: if shape: return CShape(*shape) @@ -1012,7 +1013,7 @@ def _str_to_dtype(value: int) -> Dtype: def _process_c_function( - lhs: int | float | bool | complex | Array, rhs: int | float | bool | complex | Array, + lhs: Union[int, float, Array], rhs: Union[int, float, Array], c_function: Any) -> Array: out = Array() @@ -1020,14 +1021,14 @@ def _process_c_function( lhs_array = lhs.arr rhs_array = rhs.arr - elif isinstance(lhs, Array) and isinstance(rhs, int | float | bool | complex): + elif isinstance(lhs, Array) and isinstance(rhs, (int, float)): rhs_dtype = _implicit_dtype(rhs, lhs.dtype) rhs_constant_array = _constant_array(rhs, CShape(*lhs.shape), rhs_dtype) lhs_array = lhs.arr rhs_array = rhs_constant_array.arr - elif isinstance(lhs, int | float | bool | complex) and isinstance(rhs, Array): + elif isinstance(lhs, (int, float)) and isinstance(rhs, Array): lhs_dtype = _implicit_dtype(lhs, rhs.dtype) lhs_constant_array = _constant_array(lhs, CShape(*rhs.shape), lhs_dtype) @@ -1042,7 +1043,7 @@ def _process_c_function( return out -def _implicit_dtype(value: int | float | bool | complex, array_dtype: Dtype) -> Dtype: +def _implicit_dtype(value: Union[int, float], array_dtype: Dtype) -> Dtype: if isinstance(value, bool): value_dtype = af_bool if isinstance(value, int): @@ -1066,7 +1067,7 @@ def _implicit_dtype(value: int | float | bool | complex, array_dtype: Dtype) -> return value_dtype -def _constant_array(value: int | float | bool | complex, shape: CShape, dtype: Dtype) -> Array: +def _constant_array(value: Union[int, float], shape: CShape, dtype: Dtype) -> Array: out = Array() if isinstance(value, complex): diff --git a/arrayfire/array_api/dtypes.py b/arrayfire/array_api/dtypes.py index 7f099730f..0059bf9cf 100644 --- a/arrayfire/array_api/dtypes.py +++ b/arrayfire/array_api/dtypes.py @@ -55,17 +55,3 @@ def __repr__(self) -> str: 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)) - - -# @safe_call -# def backend() -# ... - -# @backend(safe=True) -# def af_get_type(arr) -> ...: -# dty = ctypes.c_int() -# safe_call(backend.get().af_get_type(ctypes.pointer(dty), self.arr)) # -> new dty -# return dty - -# def new_dtype(): -# return af_get_type(self.arr) From 0de99554acd96ecf685c09c462d35a4d37a3e596 Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 5 Feb 2023 01:18:05 +0200 Subject: [PATCH 25/36] Add readme with reference to run tests --- arrayfire/array_api/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 arrayfire/array_api/README.md diff --git a/arrayfire/array_api/README.md b/arrayfire/array_api/README.md new file mode 100644 index 000000000..df444ed68 --- /dev/null +++ b/arrayfire/array_api/README.md @@ -0,0 +1,9 @@ +# ArrayFire ArrayAPI + +Specification Documentation: [source](https://data-apis.org/array-api/latest/purpose_and_scope.html) + +Run tests + +```bash +python -m pytest arrayfire/array_api +``` From ae6be056ee6cb6e13f41697c076bae6a343ed4ba Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 5 Feb 2023 05:01:09 +0200 Subject: [PATCH 26/36] Revert changes accidentally made in original array --- arrayfire/array.py | 67 +++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/arrayfire/array.py b/arrayfire/array.py index bf71ac227..1b71db2c7 100644 --- a/arrayfire/array.py +++ b/arrayfire/array.py @@ -11,8 +11,6 @@ Array class and helper functions. """ -from .algorithm import sum, count -from .arith import cast import inspect import os from .library import * @@ -27,7 +25,6 @@ _display_dims_limit = None - def set_display_dims_limit(*dims): """ Sets the dimension limit after which array's data won't get @@ -47,7 +44,6 @@ def set_display_dims_limit(*dims): global _display_dims_limit _display_dims_limit = dims - def get_display_dims_limit(): """ Gets the dimension limit after which array's data won't get @@ -71,7 +67,6 @@ def get_display_dims_limit(): """ return _display_dims_limit - def _in_display_dims_limit(dims): if _is_running_in_py_charm: return False @@ -85,7 +80,6 @@ def _in_display_dims_limit(dims): return False return True - def _create_array(buf, numdims, idims, dtype, is_device): out_arr = c_void_ptr_t(0) c_dims = dim4(idims[0], idims[1], idims[2], idims[3]) @@ -97,7 +91,6 @@ def _create_array(buf, numdims, idims, dtype, is_device): numdims, c_pointer(c_dims), dtype.value)) return out_arr - def _create_strided_array(buf, numdims, idims, dtype, is_device, offset, strides): out_arr = c_void_ptr_t(0) c_dims = dim4(idims[0], idims[1], idims[2], idims[3]) @@ -119,15 +112,16 @@ def _create_strided_array(buf, numdims, idims, dtype, is_device, offset, strides location.value)) return out_arr - def _create_empty_array(numdims, idims, dtype): out_arr = c_void_ptr_t(0) + + if numdims == 0: return out_arr + c_dims = dim4(idims[0], idims[1], idims[2], idims[3]) safe_call(backend.get().af_create_handle(c_pointer(out_arr), numdims, c_pointer(c_dims), dtype.value)) return out_arr - def constant_array(val, d0, d1=None, d2=None, d3=None, dtype=Dtype.f32): """ Internal function to create a C array. Should not be used externall. @@ -182,7 +176,6 @@ def _binary_func(lhs, rhs, c_func): return out - def _binary_funcr(lhs, rhs, c_func): out = Array() other = lhs @@ -199,10 +192,9 @@ def _binary_funcr(lhs, rhs, c_func): return out - def _ctype_to_lists(ctype_arr, dim, shape, offset=0): if (dim == 0): - return list(ctype_arr[offset: offset + shape[0]]) + return list(ctype_arr[offset : offset + shape[0]]) else: dim_len = shape[dim] res = [[]] * dim_len @@ -211,7 +203,6 @@ def _ctype_to_lists(ctype_arr, dim, shape, offset=0): offset += shape[0] return res - def _slice_to_length(key, dim): tkey = [key.start, key.stop, key.step] @@ -230,7 +221,6 @@ def _slice_to_length(key, dim): return int(((tkey[1] - tkey[0] - 1) / tkey[2]) + 1) - def _get_info(dims, buf_len): elements = 1 numdims = 0 @@ -260,7 +250,6 @@ def _get_indices(key): return inds - def _get_assign_dims(key, idims): dims = [1]*4 @@ -307,7 +296,6 @@ def _get_assign_dims(key, idims): else: raise IndexError("Invalid type while assigning to arrayfire.array") - def transpose(a, conj=False): """ Perform the transpose on an input. @@ -330,7 +318,6 @@ def transpose(a, conj=False): safe_call(backend.get().af_transpose(c_pointer(out.arr), a.arr, conj)) return out - def transpose_inplace(a, conj=False): """ Perform inplace transpose on an input. @@ -351,7 +338,6 @@ def transpose_inplace(a, conj=False): """ safe_call(backend.get().af_transpose_inplace(a.arr, conj)) - class Array(BaseArray): """ @@ -461,8 +447,8 @@ def __init__(self, src=None, dims=None, dtype=None, is_device=False, offset=None super(Array, self).__init__() - buf = None - buf_len = 0 + buf=None + buf_len=0 if dtype is not None: if isinstance(dtype, str): @@ -472,7 +458,7 @@ def __init__(self, src=None, dims=None, dtype=None, is_device=False, offset=None else: type_char = None - _type_char = 'f' + _type_char='f' if src is not None: @@ -483,12 +469,12 @@ def __init__(self, src=None, dims=None, dtype=None, is_device=False, offset=None host = __import__("array") if isinstance(src, host.array): - buf, buf_len = src.buffer_info() + buf,buf_len = src.buffer_info() _type_char = src.typecode numdims, idims = _get_info(dims, buf_len) elif isinstance(src, list): tmp = host.array('f', src) - buf, buf_len = tmp.buffer_info() + buf,buf_len = tmp.buffer_info() _type_char = tmp.typecode numdims, idims = _get_info(dims, buf_len) elif isinstance(src, int) or isinstance(src, c_void_ptr_t): @@ -512,7 +498,7 @@ def __init__(self, src=None, dims=None, dtype=None, is_device=False, offset=None raise TypeError("src is an object of unsupported class") if (type_char is not None and - type_char != _type_char): + type_char != _type_char): raise TypeError("Can not create array of requested type from input data type") if(offset is None and strides is None): self.arr = _create_array(buf, numdims, idims, to_dtype[_type_char], is_device) @@ -634,8 +620,8 @@ def strides(self): s2 = c_dim_t(0) s3 = c_dim_t(0) safe_call(backend.get().af_get_strides(c_pointer(s0), c_pointer(s1), - c_pointer(s2), c_pointer(s3), self.arr)) - strides = (s0.value, s1.value, s2.value, s3.value) + c_pointer(s2), c_pointer(s3), self.arr)) + strides = (s0.value,s1.value,s2.value,s3.value) return strides[:self.numdims()] def elements(self): @@ -694,8 +680,8 @@ def dims(self): d2 = c_dim_t(0) d3 = c_dim_t(0) safe_call(backend.get().af_get_dims(c_pointer(d0), c_pointer(d1), - c_pointer(d2), c_pointer(d3), self.arr)) - dims = (d0.value, d1.value, d2.value, d3.value) + c_pointer(d2), c_pointer(d3), self.arr)) + dims = (d0.value,d1.value,d2.value,d3.value) return dims[:self.numdims()] @property @@ -920,7 +906,7 @@ def __itruediv__(self, other): """ Perform self /= other. """ - self = _binary_func(self, other, backend.get().af_div) + self = _binary_func(self, other, backend.get().af_div) return self def __rtruediv__(self, other): @@ -939,7 +925,7 @@ def __idiv__(self, other): """ Perform other / self. """ - self = _binary_func(self, other, backend.get().af_div) + self = _binary_func(self, other, backend.get().af_div) return self def __rdiv__(self, other): @@ -958,7 +944,7 @@ def __imod__(self, other): """ Perform self %= other. """ - self = _binary_func(self, other, backend.get().af_mod) + self = _binary_func(self, other, backend.get().af_mod) return self def __rmod__(self, other): @@ -977,7 +963,7 @@ def __ipow__(self, other): """ Perform self **= other. """ - self = _binary_func(self, other, backend.get().af_pow) + self = _binary_func(self, other, backend.get().af_pow) return self def __rpow__(self, other): @@ -1120,7 +1106,7 @@ def logical_and(self, other): Return self && other. """ out = Array() - safe_call(backend.get().af_and(c_pointer(out.arr), self.arr, other.arr)) # TODO: bcast var? + safe_call(backend.get().af_and(c_pointer(out.arr), self.arr, other.arr)) #TODO: bcast var? return out def logical_or(self, other): @@ -1128,7 +1114,7 @@ def logical_or(self, other): Return self || other. """ out = Array() - safe_call(backend.get().af_or(c_pointer(out.arr), self.arr, other.arr)) # TODO: bcast var? + safe_call(backend.get().af_or(c_pointer(out.arr), self.arr, other.arr)) #TODO: bcast var? return out def __nonzero__(self): @@ -1158,11 +1144,12 @@ def __getitem__(self, key): inds = _get_indices(key) safe_call(backend.get().af_index_gen(c_pointer(out.arr), - self.arr, c_dim_t(n_dims), inds.pointer)) + self.arr, c_dim_t(n_dims), inds.pointer)) return out except RuntimeError as e: raise IndexError(str(e)) + def __setitem__(self, key, val): """ Perform self[key] = val @@ -1188,14 +1175,14 @@ def __setitem__(self, key, val): n_dims = 1 other_arr = constant_array(val, int(num), dtype=self.type()) else: - other_arr = constant_array(val, tdims[0], tdims[1], tdims[2], tdims[3], self.type()) + other_arr = constant_array(val, tdims[0] , tdims[1], tdims[2], tdims[3], self.type()) del_other = True else: other_arr = val.arr del_other = False out_arr = c_void_ptr_t(0) - inds = _get_indices(key) + inds = _get_indices(key) safe_call(backend.get().af_assign_gen(c_pointer(out_arr), self.arr, c_dim_t(n_dims), inds.pointer, @@ -1414,7 +1401,6 @@ def to_ndarray(self, output=None): safe_call(backend.get().af_get_data_ptr(c_void_ptr_t(output.ctypes.data), tmp.arr)) return output - def display(a, precision=4): """ Displays the contents of an array. @@ -1440,7 +1426,6 @@ def display(a, precision=4): safe_call(backend.get().af_print_array_gen(name.encode('utf-8'), a.arr, c_int_t(precision))) - def save_array(key, a, filename, append=False): """ Save an array to disk. @@ -1472,7 +1457,6 @@ def save_array(key, a, filename, append=False): append)) return index.value - def read_array(filename, index=None, key=None): """ Read an array from disk. @@ -1506,3 +1490,6 @@ def read_array(filename, index=None, key=None): key.encode('utf-8'))) return out + +from .algorithm import (sum, count) +from .arith import cast From cfa91143c27ef5d20666111b06f9cc2fbc526b20 Mon Sep 17 00:00:00 2001 From: Anton Date: Thu, 9 Feb 2023 01:13:25 +0200 Subject: [PATCH 27/36] Add initial refactoring with backend mock --- arrayfire/array_api/array_object.py | 20 ++++------- arrayfire/array_api/backend/__init__.py | 25 ++++++++++++++ arrayfire/array_api/backend/constants.py | 38 +++++++++++++++++++++ arrayfire/array_api/backend/library.py | 8 +++++ arrayfire/array_api/dtypes.py | 4 +++ arrayfire/array_api/operators.py | 10 ++++++ arrayfire/array_api/tests/test_operators.py | 15 ++++++++ arrayfire/array_api/utils.py | 6 ++-- 8 files changed, 111 insertions(+), 15 deletions(-) create mode 100644 arrayfire/array_api/backend/__init__.py create mode 100644 arrayfire/array_api/backend/constants.py create mode 100644 arrayfire/array_api/backend/library.py create mode 100644 arrayfire/array_api/operators.py create mode 100644 arrayfire/array_api/tests/test_operators.py diff --git a/arrayfire/array_api/array_object.py b/arrayfire/array_api/array_object.py index 30977895f..7975d0b32 100644 --- a/arrayfire/array_api/array_object.py +++ b/arrayfire/array_api/array_object.py @@ -20,7 +20,7 @@ from .dtypes import float32 as af_float32 from .dtypes import float64 as af_float64 from .dtypes import int64 as af_int64 -from .dtypes import supported_dtypes +from .dtypes import supported_dtypes, to_str from .dtypes import uint64 as af_uint64 ShapeType = Tuple[int, ...] @@ -970,7 +970,7 @@ def _array_as_str(array: Array) -> str: arr_str = ctypes.c_char_p(0) # FIXME add description to passed arguments safe_call(backend.get().af_array_to_string(ctypes.pointer(arr_str), "", array.arr, 4, True)) - py_str = _to_str(arr_str) + py_str = to_str(arr_str) safe_call(backend.get().af_free_host(arr_str)) return py_str @@ -1000,10 +1000,6 @@ def _c_api_value_to_dtype(value: int) -> Dtype: raise TypeError("There is no supported dtype that matches passed dtype C API value.") -def _to_str(c_str: ctypes.c_char_p) -> str: - return str(c_str.value.decode("utf-8")) # type: ignore[union-attr] - - def _str_to_dtype(value: int) -> Dtype: for dtype in supported_dtypes: if value == dtype.typecode or value == dtype.typename: @@ -1067,7 +1063,7 @@ def _implicit_dtype(value: Union[int, float], array_dtype: Dtype) -> Dtype: return value_dtype -def _constant_array(value: Union[int, float], shape: CShape, dtype: Dtype) -> Array: +def _constant_array(value: Union[int, float], cshape: CShape, dtype: Dtype) -> Array: out = Array() if isinstance(value, complex): @@ -1076,18 +1072,16 @@ def _constant_array(value: Union[int, float], shape: CShape, dtype: Dtype) -> Ar safe_call(backend.get().af_constant_complex( ctypes.pointer(out.arr), ctypes.c_double(value.real), ctypes.c_double(value.imag), 4, - ctypes.pointer(shape.c_array), dtype.c_api_value)) + ctypes.pointer(cshape.c_array), dtype.c_api_value)) elif dtype == af_int64: # TODO discuss workaround for passing float to ctypes safe_call(backend.get().af_constant_long( - ctypes.pointer(out.arr), ctypes.c_longlong(value.real), # type: ignore[arg-type] - 4, ctypes.pointer(shape.c_array))) + ctypes.pointer(out.arr), af_int64.c_type(value.real), 4, ctypes.pointer(cshape.c_array))) elif dtype == af_uint64: safe_call(backend.get().af_constant_ulong( - ctypes.pointer(out.arr), ctypes.c_ulonglong(value.real), # type: ignore[arg-type] - 4, ctypes.pointer(shape.c_array))) + ctypes.pointer(out.arr), af_uint64.c_type(value.real), 4, ctypes.pointer(cshape.c_array))) else: safe_call(backend.get().af_constant( - ctypes.pointer(out.arr), ctypes.c_double(value), 4, ctypes.pointer(shape.c_array), dtype.c_api_value)) + ctypes.pointer(out.arr), ctypes.c_double(value), 4, ctypes.pointer(cshape.c_array), dtype.c_api_value)) return out diff --git a/arrayfire/array_api/backend/__init__.py b/arrayfire/array_api/backend/__init__.py new file mode 100644 index 000000000..994f8b322 --- /dev/null +++ b/arrayfire/array_api/backend/__init__.py @@ -0,0 +1,25 @@ +import ctypes +from typing import Any, Callable + +from ..dtypes import c_dim_t, to_str +from .constants import ErrorCodes + +backend = ctypes.CDLL("/opt/arrayfire//lib/libafcpu.3.dylib") # Mock + + +Pointer = ctypes._Pointer +AFArray = ctypes.c_void_p + + +class safe_call: + def __init__(self, c_function: Callable) -> None: + self.c_function = c_function + + def __call__(self, *args: Any) -> None: + c_err = self.c_function(*args) + if c_err == ErrorCodes.none.value: + return + + err_str = ctypes.c_char_p(0) + backend.af_get_last_error(ctypes.pointer(err_str), ctypes.pointer(c_dim_t(0))) + raise RuntimeError(to_str(err_str)) diff --git a/arrayfire/array_api/backend/constants.py b/arrayfire/array_api/backend/constants.py new file mode 100644 index 000000000..58b889928 --- /dev/null +++ b/arrayfire/array_api/backend/constants.py @@ -0,0 +1,38 @@ +import enum + + +class ErrorCodes(enum.Enum): + none = 0 + + # 100-199 Errors in environment + no_mem = 101 + driver = 102 + runtime = 103 + + # 200-299 Errors in input parameters + invalid_array = 201 + arg = 202 + size = 203 + type = 204 + diff_type = 205 + batch = 207 + device = 208 + + # 300-399 Errors for missing software features + not_supported = 301 + not_configured = 302 + nonfree = 303 + + # 400-499 Errors for missing hardware features + no_dbl = 401 + no_gfx = 402 + no_half = 403 + + # 500-599 Errors specific to the heterogeneous API + load_lib = 501 + load_sym = 502 + arr_bknd_mismatch = 503 + + # 900-999 Errors from upstream libraries and runtimes + internal = 998 + unknown = 999 diff --git a/arrayfire/array_api/backend/library.py b/arrayfire/array_api/backend/library.py new file mode 100644 index 000000000..4f063fd3b --- /dev/null +++ b/arrayfire/array_api/backend/library.py @@ -0,0 +1,8 @@ +from typing import Any + +from . import AFArray, Pointer, backend, safe_call + + +@safe_call +def af_add(out: Pointer, lhs: AFArray, rhs: AFArray, batch: bool) -> Any: + return backend.af_add(out, lhs, rhs, batch) diff --git a/arrayfire/array_api/dtypes.py b/arrayfire/array_api/dtypes.py index 0059bf9cf..bd77c546c 100644 --- a/arrayfire/array_api/dtypes.py +++ b/arrayfire/array_api/dtypes.py @@ -55,3 +55,7 @@ def __repr__(self) -> str: 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] diff --git a/arrayfire/array_api/operators.py b/arrayfire/array_api/operators.py new file mode 100644 index 000000000..f6998111c --- /dev/null +++ b/arrayfire/array_api/operators.py @@ -0,0 +1,10 @@ +import ctypes + +from .array_object import Array +from .backend import library + + +def add(x1: Array, x2: Array, /) -> Array: + out = Array() + library.af_add(ctypes.pointer(out.arr), x1.arr, x2.arr, False) + return out diff --git a/arrayfire/array_api/tests/test_operators.py b/arrayfire/array_api/tests/test_operators.py new file mode 100644 index 000000000..93bd6e36a --- /dev/null +++ b/arrayfire/array_api/tests/test_operators.py @@ -0,0 +1,15 @@ +from typing import Any + +from arrayfire.array_api import operators +from arrayfire.array_api.array_object import Array + + +class TestArithmeticOperators: + def setup_method(self, method: Any) -> None: + self.array1 = Array([1, 2, 3]) + self.array2 = Array([4, 5, 6]) + + def test_add(self) -> None: + res = operators.add(self.array1, self.array2) + res_sum = self.array1 + self.array2 + assert res.to_list() == res_sum.to_list() == [5, 7, 9] diff --git a/arrayfire/array_api/utils.py b/arrayfire/array_api/utils.py index 779459efb..195111d33 100644 --- a/arrayfire/array_api/utils.py +++ b/arrayfire/array_api/utils.py @@ -1,11 +1,13 @@ +from typing import Tuple, Union + from .array_object import Array # TODO implement functions -def all(x: Array, /, *, axis: None | int | tuple[int, ...] = None, keepdims: bool = False) -> Array: +def all(x: Array, /, *, axis: Union[None, int, Tuple[int, ...]] = None, keepdims: bool = False) -> Array: return NotImplemented -def any(x: Array, /, *, axis: None | int | tuple[int, ...] = None, keepdims: bool = False) -> Array: +def any(x: Array, /, *, axis: Union[None, int, Tuple[int, ...]] = None, keepdims: bool = False) -> Array: return NotImplemented From 5de869414cde08760ac11fef55fea3c43cefe738 Mon Sep 17 00:00:00 2001 From: Anton Date: Thu, 9 Feb 2023 02:24:54 +0200 Subject: [PATCH 28/36] Add c library methods for operators --- arrayfire/array_api/array_object.py | 109 +++++----- arrayfire/array_api/backend/__init__.py | 4 - arrayfire/array_api/backend/library.py | 203 +++++++++++++++++- arrayfire/array_api/operators.py | 6 + .../array_api/tests/test_array_object.py | 2 +- arrayfire/array_api/tests/test_operators.py | 5 + 6 files changed, 267 insertions(+), 62 deletions(-) diff --git a/arrayfire/array_api/array_object.py b/arrayfire/array_api/array_object.py index 7975d0b32..fb1d3427d 100644 --- a/arrayfire/array_api/array_object.py +++ b/arrayfire/array_api/array_object.py @@ -11,6 +11,7 @@ from arrayfire.algorithm import count from arrayfire.array import _get_indices, _in_display_dims_limit +from .backend import library from .device import PointerSource from .dtypes import CShape, Dtype from .dtypes import bool as af_bool @@ -56,17 +57,17 @@ def __init__( if x is None: if not shape: # shape is None or empty tuple - safe_call(backend.get().af_create_handle( - ctypes.pointer(self.arr), 0, ctypes.pointer(CShape().c_array), dtype.c_api_value)) + library.af_create_handle( + ctypes.pointer(self.arr), 0, ctypes.pointer(CShape().c_array), dtype.c_api_value) return # NOTE: applies inplace changes for self.arr - safe_call(backend.get().af_create_handle( - ctypes.pointer(self.arr), len(shape), ctypes.pointer(CShape(*shape).c_array), dtype.c_api_value)) + library.af_create_handle( + ctypes.pointer(self.arr), len(shape), ctypes.pointer(CShape(*shape).c_array), dtype.c_api_value) return if isinstance(x, Array): - safe_call(backend.get().af_retain_array(ctypes.pointer(self.arr), x.arr)) + library.af_retain_array(ctypes.pointer(self.arr), x.arr) return if isinstance(x, py_array.array): @@ -99,14 +100,14 @@ def __init__( if not (offset or strides): if pointer_source == PointerSource.host: - safe_call(backend.get().af_create_array( + library.af_create_array( ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), _cshape.original_shape, - ctypes.pointer(_cshape.c_array), dtype.c_api_value)) + ctypes.pointer(_cshape.c_array), dtype.c_api_value) return - safe_call(backend.get().af_device_array( + library.af_device_array( ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), _cshape.original_shape, - ctypes.pointer(_cshape.c_array), dtype.c_api_value)) + ctypes.pointer(_cshape.c_array), dtype.c_api_value) return if offset is None: @@ -119,10 +120,10 @@ def __init__( strides += (strides[-1], ) * (4 - len(strides)) strides_cshape = CShape(*strides).c_array - safe_call(backend.get().af_create_strided_array( + library.af_create_strided_array( ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), offset, _cshape.original_shape, ctypes.pointer(_cshape.c_array), ctypes.pointer(strides_cshape), dtype.c_api_value, - pointer_source.value)) + pointer_source.value) # Arithmetic Operators @@ -178,7 +179,7 @@ def __add__(self, other: Union[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, backend.get().af_add) + return _process_c_function(self, other, library.af_add) def __sub__(self, other: Union[int, float, Array], /) -> Array: """ @@ -200,7 +201,7 @@ def __sub__(self, other: Union[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, backend.get().af_sub) + return _process_c_function(self, other, library.af_sub) def __mul__(self, other: Union[int, float, Array], /) -> Array: """ @@ -219,7 +220,7 @@ def __mul__(self, other: Union[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, backend.get().af_mul) + return _process_c_function(self, other, library.af_mul) def __truediv__(self, other: Union[int, float, Array], /) -> Array: """ @@ -246,7 +247,7 @@ def __truediv__(self, other: Union[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, backend.get().af_div) + return _process_c_function(self, other, library.af_div) def __floordiv__(self, other: Union[int, float, Array], /) -> Array: # TODO @@ -276,7 +277,7 @@ def __mod__(self, other: Union[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, backend.get().af_mod) + return _process_c_function(self, other, library.af_mod) def __pow__(self, other: Union[int, float, Array], /) -> Array: """ @@ -298,7 +299,7 @@ def __pow__(self, other: Union[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, backend.get().af_pow) + return _process_c_function(self, other, library.af_pow) # Array Operators @@ -323,7 +324,7 @@ def __invert__(self) -> Array: An array containing the element-wise results. The returned array must have the same data type as self. """ out = Array() - safe_call(backend.get().af_bitnot(ctypes.pointer(out.arr), self.arr)) + library.af_bitnot(ctypes.pointer(out.arr), self.arr) return out def __and__(self, other: Union[int, bool, Array], /) -> Array: @@ -344,7 +345,7 @@ def __and__(self, other: Union[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, backend.get().af_bitand) + return _process_c_function(self, other, library.af_bitand) def __or__(self, other: Union[int, bool, Array], /) -> Array: """ @@ -364,7 +365,7 @@ def __or__(self, other: Union[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, backend.get().af_bitor) + return _process_c_function(self, other, library.af_bitor) def __xor__(self, other: Union[int, bool, Array], /) -> Array: """ @@ -384,7 +385,7 @@ def __xor__(self, other: Union[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, backend.get().af_bitxor) + return _process_c_function(self, other, library.af_bitxor) def __lshift__(self, other: Union[int, Array], /) -> Array: """ @@ -404,7 +405,7 @@ def __lshift__(self, other: Union[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, backend.get().af_bitshiftl) + return _process_c_function(self, other, library.af_bitshiftl) def __rshift__(self, other: Union[int, Array], /) -> Array: """ @@ -424,7 +425,7 @@ def __rshift__(self, other: Union[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, backend.get().af_bitshiftr) + return _process_c_function(self, other, library.af_bitshiftr) # Comparison Operators @@ -445,7 +446,7 @@ def __lt__(self, other: Union[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, backend.get().af_lt) + return _process_c_function(self, other, library.af_lt) def __le__(self, other: Union[int, float, Array], /) -> Array: """ @@ -464,7 +465,7 @@ def __le__(self, other: Union[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, backend.get().af_le) + return _process_c_function(self, other, library.af_le) def __gt__(self, other: Union[int, float, Array], /) -> Array: """ @@ -483,7 +484,7 @@ def __gt__(self, other: Union[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, backend.get().af_gt) + return _process_c_function(self, other, library.af_gt) def __ge__(self, other: Union[int, float, Array], /) -> Array: """ @@ -502,9 +503,9 @@ def __ge__(self, other: Union[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, backend.get().af_ge) + return _process_c_function(self, other, library.af_ge) - def __eq__(self, other: Union[int, float, bool, Array], /) -> Array: # type: ignore[override] # FIXME + def __eq__(self, other: Union[int, float, bool, Array], /) -> Array: """ Computes the truth value of self_i == other_i for each element of an array instance with the respective element of the array other. @@ -521,9 +522,9 @@ def __eq__(self, other: Union[int, float, bool, Array], /) -> Array: # type: ig 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, backend.get().af_eq) + return _process_c_function(self, other, library.af_eq) - def __ne__(self, other: Union[int, float, bool, Array], /) -> Array: # type: ignore[override] # FIXME + def __ne__(self, other: Union[int, float, bool, Array], /) -> Array: """ Computes the truth value of self_i != other_i for each element of an array instance with the respective element of the array other. @@ -540,7 +541,7 @@ def __ne__(self, other: Union[int, float, bool, Array], /) -> Array: # type: ig 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, backend.get().af_neq) + return _process_c_function(self, other, library.af_neq) # Reflected Arithmetic Operators @@ -548,25 +549,25 @@ def __radd__(self, other: Array, /) -> Array: """ Return other + self. """ - return _process_c_function(other, self, backend.get().af_add) + return _process_c_function(other, self, library.af_add) def __rsub__(self, other: Array, /) -> Array: """ Return other - self. """ - return _process_c_function(other, self, backend.get().af_sub) + return _process_c_function(other, self, library.af_sub) def __rmul__(self, other: Array, /) -> Array: """ Return other * self. """ - return _process_c_function(other, self, backend.get().af_mul) + return _process_c_function(other, self, library.af_mul) def __rtruediv__(self, other: Array, /) -> Array: """ Return other / self. """ - return _process_c_function(other, self, backend.get().af_div) + return _process_c_function(other, self, library.af_div) def __rfloordiv__(self, other: Array, /) -> Array: # TODO @@ -576,13 +577,13 @@ def __rmod__(self, other: Array, /) -> Array: """ Return other % self. """ - return _process_c_function(other, self, backend.get().af_mod) + return _process_c_function(other, self, library.af_mod) def __rpow__(self, other: Array, /) -> Array: """ Return other ** self. """ - return _process_c_function(other, self, backend.get().af_pow) + return _process_c_function(other, self, library.af_pow) # Reflected Array Operators @@ -596,31 +597,31 @@ def __rand__(self, other: Array, /) -> Array: """ Return other & self. """ - return _process_c_function(other, self, backend.get().af_bitand) + return _process_c_function(other, self, library.af_bitand) def __ror__(self, other: Array, /) -> Array: """ Return other | self. """ - return _process_c_function(other, self, backend.get().af_bitor) + return _process_c_function(other, self, library.af_bitor) def __rxor__(self, other: Array, /) -> Array: """ Return other ^ self. """ - return _process_c_function(other, self, backend.get().af_bitxor) + return _process_c_function(other, self, library.af_bitxor) def __rlshift__(self, other: Array, /) -> Array: """ Return other << self. """ - return _process_c_function(other, self, backend.get().af_bitshiftl) + return _process_c_function(other, self, library.af_bitshiftl) def __rrshift__(self, other: Array, /) -> Array: """ Return other >> self. """ - return _process_c_function(other, self, backend.get().af_bitshiftr) + return _process_c_function(other, self, library.af_bitshiftr) # In-place Arithmetic Operators @@ -629,25 +630,25 @@ def __iadd__(self, other: Union[int, float, Array], /) -> Array: """ Return self += other. """ - return _process_c_function(self, other, backend.get().af_add) + return _process_c_function(self, other, library.af_add) def __isub__(self, other: Union[int, float, Array], /) -> Array: """ Return self -= other. """ - return _process_c_function(self, other, backend.get().af_sub) + return _process_c_function(self, other, library.af_sub) def __imul__(self, other: Union[int, float, Array], /) -> Array: """ Return self *= other. """ - return _process_c_function(self, other, backend.get().af_mul) + return _process_c_function(self, other, library.af_mul) def __itruediv__(self, other: Union[int, float, Array], /) -> Array: """ Return self /= other. """ - return _process_c_function(self, other, backend.get().af_div) + return _process_c_function(self, other, library.af_div) def __ifloordiv__(self, other: Union[int, float, Array], /) -> Array: # TODO @@ -657,13 +658,13 @@ def __imod__(self, other: Union[int, float, Array], /) -> Array: """ Return self %= other. """ - return _process_c_function(self, other, backend.get().af_mod) + return _process_c_function(self, other, library.af_mod) def __ipow__(self, other: Union[int, float, Array], /) -> Array: """ Return self **= other. """ - return _process_c_function(self, other, backend.get().af_pow) + return _process_c_function(self, other, library.af_pow) # In-place Array Operators @@ -677,31 +678,31 @@ def __iand__(self, other: Union[int, bool, Array], /) -> Array: """ Return self &= other. """ - return _process_c_function(self, other, backend.get().af_bitand) + return _process_c_function(self, other, library.af_bitand) def __ior__(self, other: Union[int, bool, Array], /) -> Array: """ Return self |= other. """ - return _process_c_function(self, other, backend.get().af_bitor) + return _process_c_function(self, other, library.af_bitor) def __ixor__(self, other: Union[int, bool, Array], /) -> Array: """ Return self ^= other. """ - return _process_c_function(self, other, backend.get().af_bitxor) + return _process_c_function(self, other, library.af_bitxor) def __ilshift__(self, other: Union[int, Array], /) -> Array: """ Return self <<= other. """ - return _process_c_function(self, other, backend.get().af_bitshiftl) + return _process_c_function(self, other, library.af_bitshiftl) def __irshift__(self, other: Union[int, Array], /) -> Array: """ Return self >>= other. """ - return _process_c_function(self, other, backend.get().af_bitshiftr) + return _process_c_function(self, other, library.af_bitshiftr) # Methods @@ -1034,7 +1035,7 @@ def _process_c_function( else: raise TypeError(f"{type(rhs)} is not supported and can not be passed to C binary function.") - safe_call(c_function(ctypes.pointer(out.arr), lhs_array, rhs_array, _bcast_var)) + c_function(ctypes.pointer(out.arr), lhs_array, rhs_array, _bcast_var) return out diff --git a/arrayfire/array_api/backend/__init__.py b/arrayfire/array_api/backend/__init__.py index 994f8b322..26110e3a1 100644 --- a/arrayfire/array_api/backend/__init__.py +++ b/arrayfire/array_api/backend/__init__.py @@ -7,10 +7,6 @@ backend = ctypes.CDLL("/opt/arrayfire//lib/libafcpu.3.dylib") # Mock -Pointer = ctypes._Pointer -AFArray = ctypes.c_void_p - - class safe_call: def __init__(self, c_function: Callable) -> None: self.c_function = c_function diff --git a/arrayfire/array_api/backend/library.py b/arrayfire/array_api/backend/library.py index 4f063fd3b..b1423700c 100644 --- a/arrayfire/array_api/backend/library.py +++ b/arrayfire/array_api/backend/library.py @@ -1,8 +1,205 @@ -from typing import Any +import ctypes +from typing import Any, Type -from . import AFArray, Pointer, backend, safe_call +from ..dtypes import c_dim_t +from . import backend, safe_call + +AFArrayPointer = ctypes._Pointer +AFArray = ctypes.c_void_p +Dim_T = Type[c_dim_t] +Pointer = ctypes._Pointer + +# Array management + + +@safe_call +def af_create_handle(arr: AFArrayPointer, ndims: int, dims: Any, type_: int, /) -> Any: # FIXME dims typing + """ + source: https://arrayfire.org/docs/group__c__api__mat.htm#ga3b8f5cf6fce69aa1574544bc2d44d7d0 + """ + return backend.af_create_handle(arr, ndims, dims, type_) + + +@safe_call +def af_retain_array(out: AFArrayPointer, in_: AFArray, /) -> Any: + """ + source: https://arrayfire.org/docs/group__c__api__mat.htm#ga7ed45b3f881c0f6c80c5cf2af886dbab + """ + return backend.af_retain_array(out, in_) + + +@safe_call +def af_create_array( + arr: AFArrayPointer, data: ctypes.c_void_p, ndims: int, dims: ..., type_: int, /) -> Any: # FIXME dims typing + """ + source: https://arrayfire.org/docs/group__c__api__mat.htm#ga834be32357616d8ab735087c6f681858 + """ + return backend.af_create_array(arr, data, ndims, dims, type_) + + +@safe_call +def af_device_array( + arr: AFArrayPointer, data: ctypes.c_void_p, ndims: int, dims: ..., type_: int, /) -> Any: # FIXME dims typing + """ + source: https://arrayfire.org/docs/group__c__api__mat.htm#gaad4fc77f872217e7337cb53bfb623cf5 + """ + return backend.af_device_array(arr, data, ndims, dims, type_) + + +@safe_call +def af_create_strided_array( + arr: AFArrayPointer, data: ctypes.c_void_p, offset: Dim_T, ndims: int, dims: ..., strides: ..., + ty: int, location: int, /) -> Any: # FIXME dims, strides typing + """ + source: https://arrayfire.org/docs/group__internal__func__create.htm#gad31241a3437b7b8bc3cf49f85e5c4e0c + """ + return backend.af_create_strided_array(arr, data, offset, ndims, dims, strides, ty, location) + + +# Arithmetic Operators @safe_call -def af_add(out: Pointer, lhs: AFArray, rhs: AFArray, batch: bool) -> Any: +def af_add(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__add.htm#ga1dfbee755fedd680f4476803ddfe06a7 + """ return backend.af_add(out, lhs, rhs, batch) + + +@safe_call +def af_sub(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__sub.htm#ga80ff99a2e186c23614ea9f36ffc6f0a4 + """ + return backend.af_sub(out, lhs, rhs, batch) + + +@safe_call +def af_mul(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__mul.htm#ga5f7588b2809ff7551d38b6a0bd583a02 + """ + return backend.af_mul(out, lhs, rhs, batch) + + +@safe_call +def af_div(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__div.htm#ga21f3f97755702692ec8976934e75fde6 + """ + return backend.af_div(out, lhs, rhs, batch) + + +@safe_call +def af_mod(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__mod.htm#ga01924d1b59d8886e46fabd2dc9b27e0f + """ + return backend.af_mod(out, lhs, rhs, batch) + + +@safe_call +def af_pow(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__pow.htm#ga0f28be1a9c8b176a78c4a47f483e7fc6 + """ + return backend.af_pow(out, lhs, rhs, batch) + + +# Bitwise Operators + +@safe_call +def af_bitnot(out: AFArrayPointer, arr: AFArray, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__bitnot.htm#gaf97e8a38aab59ed2d3a742515467d01e + """ + return backend.af_bitnot(out, arr) + + +@safe_call +def af_bitand(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__bitand.htm#ga45c0779ade4703708596df11cca98800 + """ + return backend.af_bitand(out, lhs, rhs, batch) + + +@safe_call +def af_bitor(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__bitor.htm#ga84c99f77d1d83fd53f949b4d67b5b210 + """ + return backend.af_bitor(out, lhs, rhs, batch) + + +@safe_call +def af_bitxor(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__bitxor.htm#ga8188620da6b432998e55fdd1fad22100 + """ + return backend.af_bitxor(out, lhs, rhs, batch) + + +@safe_call +def af_bitshiftl(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__shiftl.htm#ga3139645aafe6f045a5cab454e9c13137 + """ + return backend.af_bitshiftl(out, lhs, rhs, batch) + + +@safe_call +def af_bitshiftr(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__shiftr.htm#ga4c06b9977ecf96cdfc83b5dfd1ac4895 + """ + return backend.af_bitshiftr(out, lhs, rhs, batch) + + +@safe_call +def af_lt(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/arith_8h.htm#ae7aa04bf23b32bb11c4bab8bdd637103 + """ + return backend.af_lt(out, lhs, rhs, batch) + + +@safe_call +def af_le(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__le.htm#gad5535ce64dbed46d0773fd494e84e922 + """ + return backend.af_le(out, lhs, rhs, batch) + + +@safe_call +def af_gt(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__gt.htm#ga4e65603259515de8939899a163ebaf9e + """ + return backend.af_gt(out, lhs, rhs, batch) + + +@safe_call +def af_ge(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__ge.htm#ga4513f212e0b0a22dcf4653e89c85e3d9 + """ + return backend.af_ge(out, lhs, rhs, batch) + + +@safe_call +def af_eq(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__eq.htm#ga76d2da7716831616bb81effa9e163693 + """ + return backend.af_eq(out, lhs, rhs, batch) + + +@safe_call +def af_neq(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + """ + source: https://arrayfire.org/docs/group__arith__func__neq.htm#gae4ee8bd06a410f259f1493fb811ce441 + """ + return backend.af_neq(out, lhs, rhs, batch) diff --git a/arrayfire/array_api/operators.py b/arrayfire/array_api/operators.py index f6998111c..9447d3fa7 100644 --- a/arrayfire/array_api/operators.py +++ b/arrayfire/array_api/operators.py @@ -8,3 +8,9 @@ def add(x1: Array, x2: Array, /) -> Array: out = Array() library.af_add(ctypes.pointer(out.arr), x1.arr, x2.arr, False) return out + + +def sub(x1: Array, x2: Array, /) -> Array: + out = Array() + library.af_sub(ctypes.pointer(out.arr), x1.arr, x2.arr, False) + return out diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index f30567553..0204542cc 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -170,7 +170,7 @@ def test_array_to_list_is_empty() -> None: assert array.to_list() == [] -class TestArithmeticOperators: +class TestClassArithmeticOperators: def setup_method(self, method: Any) -> None: self.list = [1, 2, 3] self.const_int = 2 diff --git a/arrayfire/array_api/tests/test_operators.py b/arrayfire/array_api/tests/test_operators.py index 93bd6e36a..21926a27f 100644 --- a/arrayfire/array_api/tests/test_operators.py +++ b/arrayfire/array_api/tests/test_operators.py @@ -13,3 +13,8 @@ def test_add(self) -> None: res = operators.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_sum = self.array1 - self.array2 + assert res.to_list() == res_sum.to_list() == [-3, -3, -3] From b9ac1c5d57564834d0db282cdc23cd073a900068 Mon Sep 17 00:00:00 2001 From: Anton Date: Fri, 10 Feb 2023 16:00:45 +0200 Subject: [PATCH 29/36] Remove dependency on default backend --- arrayfire/array_api/array_object.py | 43 ++++---- arrayfire/array_api/backend/library.py | 130 +++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 23 deletions(-) diff --git a/arrayfire/array_api/array_object.py b/arrayfire/array_api/array_object.py index fb1d3427d..0ccf36ee8 100644 --- a/arrayfire/array_api/array_object.py +++ b/arrayfire/array_api/array_object.py @@ -7,7 +7,6 @@ from typing import Any, List, Optional, Tuple, Union # TODO replace imports from original lib with refactored ones -from arrayfire import backend, safe_call from arrayfire.algorithm import count from arrayfire.array import _get_indices, _in_display_dims_limit @@ -761,8 +760,7 @@ def __getitem__(self, key: Union[int, slice, Tuple[Union[int, slice, ], ...], Ar if count(key) == 0: return out - safe_call(backend.get().af_index_gen( - ctypes.pointer(out.arr), self.arr, c_dim_t(ndims), _get_indices(key).pointer)) + library.af_index_gen(ctypes.pointer(out.arr), self.arr, c_dim_t(ndims), _get_indices(key).pointer) return out def __index__(self) -> int: @@ -811,7 +809,7 @@ def dtype(self) -> Dtype: Array data type. """ out = ctypes.c_int() - safe_call(backend.get().af_get_type(ctypes.pointer(out), self.arr)) + library.af_get_type(ctypes.pointer(out), self.arr) return _c_api_value_to_dtype(out.value) @property @@ -845,7 +843,7 @@ def T(self) -> Array: # TODO add check if out.dtype == self.dtype out = Array() - safe_call(backend.get().af_transpose(ctypes.pointer(out.arr), self.arr, False)) + library.af_transpose(ctypes.pointer(out.arr), self.arr, False) return out @property @@ -864,7 +862,7 @@ def size(self) -> int: """ # NOTE previously - elements() out = c_dim_t(0) - safe_call(backend.get().af_get_elements(ctypes.pointer(out), self.arr)) + library.af_get_elements(ctypes.pointer(out), self.arr) return out.value @property @@ -876,7 +874,7 @@ def ndim(self) -> int: Number of array dimensions (axes). """ out = ctypes.c_uint(0) - safe_call(backend.get().af_get_numdims(ctypes.pointer(out), self.arr)) + library.af_get_numdims(ctypes.pointer(out), self.arr) return out.value @property @@ -894,8 +892,7 @@ def shape(self) -> ShapeType: d1 = c_dim_t(0) d2 = c_dim_t(0) d3 = c_dim_t(0) - safe_call(backend.get().af_get_dims( - ctypes.pointer(d0), ctypes.pointer(d1), ctypes.pointer(d2), ctypes.pointer(d3), self.arr)) + library.af_get_dims(ctypes.pointer(d0), ctypes.pointer(d1), ctypes.pointer(d2), ctypes.pointer(d3), self.arr) return (d0.value, d1.value, d2.value, d3.value)[:self.ndim] # Skip passing None values def scalar(self) -> Union[None, int, float, bool, complex]: @@ -907,7 +904,7 @@ def scalar(self) -> Union[None, int, float, bool, complex]: return None out = self.dtype.c_type() - safe_call(backend.get().af_get_scalar(ctypes.pointer(out), self.arr)) + library.af_get_scalar(ctypes.pointer(out), self.arr) return out.value # type: ignore[no-any-return] # FIXME def is_empty(self) -> bool: @@ -915,7 +912,7 @@ def is_empty(self) -> bool: Check if the array is empty i.e. it has no elements. """ out = ctypes.c_bool() - safe_call(backend.get().af_is_empty(ctypes.pointer(out), self.arr)) + library.af_is_empty(ctypes.pointer(out), self.arr) return out.value def to_list(self, row_major: bool = False) -> List[Union[None, int, float, bool, complex]]: @@ -950,7 +947,7 @@ def to_ctype_array(self, row_major: bool = False) -> ctypes.Array: def _get_ctypes_array(array: Array) -> ctypes.Array: c_shape = array.dtype.c_type * array.size ctypes_array = c_shape() - safe_call(backend.get().af_get_data_ptr(ctypes.pointer(ctypes_array), array.arr)) + library.af_get_data_ptr(ctypes.pointer(ctypes_array), array.arr) return ctypes_array @@ -963,16 +960,16 @@ def _reorder(array: Array) -> Array: out = Array() c_shape = CShape(*(tuple(reversed(range(array.ndim))) + tuple(range(array.ndim, 4)))) - safe_call(backend.get().af_reorder(ctypes.pointer(out.arr), array.arr, *c_shape)) + library.af_reorder(ctypes.pointer(out.arr), array.arr, *c_shape) return out def _array_as_str(array: Array) -> str: arr_str = ctypes.c_char_p(0) # FIXME add description to passed arguments - safe_call(backend.get().af_array_to_string(ctypes.pointer(arr_str), "", array.arr, 4, True)) + library.af_array_to_string(ctypes.pointer(arr_str), "", array.arr, 4, True) py_str = to_str(arr_str) - safe_call(backend.get().af_free_host(arr_str)) + library.af_free_host(arr_str) return py_str @@ -1071,18 +1068,18 @@ def _constant_array(value: Union[int, float], cshape: CShape, dtype: Dtype) -> A if dtype != af_complex64 and dtype != af_complex128: dtype = af_complex64 - safe_call(backend.get().af_constant_complex( + library.af_constant_complex( ctypes.pointer(out.arr), ctypes.c_double(value.real), ctypes.c_double(value.imag), 4, - ctypes.pointer(cshape.c_array), dtype.c_api_value)) + ctypes.pointer(cshape.c_array), dtype.c_api_value) elif dtype == af_int64: # TODO discuss workaround for passing float to ctypes - safe_call(backend.get().af_constant_long( - ctypes.pointer(out.arr), af_int64.c_type(value.real), 4, ctypes.pointer(cshape.c_array))) + library.af_constant_long( + ctypes.pointer(out.arr), af_int64.c_type(value.real), 4, ctypes.pointer(cshape.c_array)) elif dtype == af_uint64: - safe_call(backend.get().af_constant_ulong( - ctypes.pointer(out.arr), af_uint64.c_type(value.real), 4, ctypes.pointer(cshape.c_array))) + library.af_constant_ulong( + ctypes.pointer(out.arr), af_uint64.c_type(value.real), 4, ctypes.pointer(cshape.c_array)) else: - safe_call(backend.get().af_constant( - ctypes.pointer(out.arr), ctypes.c_double(value), 4, ctypes.pointer(cshape.c_array), dtype.c_api_value)) + library.af_constant( + ctypes.pointer(out.arr), ctypes.c_double(value), 4, ctypes.pointer(cshape.c_array), dtype.c_api_value) return out diff --git a/arrayfire/array_api/backend/library.py b/arrayfire/array_api/backend/library.py index b1423700c..0285f72bd 100644 --- a/arrayfire/array_api/backend/library.py +++ b/arrayfire/array_api/backend/library.py @@ -56,6 +56,136 @@ def af_create_strided_array( return backend.af_create_strided_array(arr, data, offset, ndims, dims, strides, ty, location) +@safe_call +def af_get_type(type_: ..., arr: AFArray, /) -> Any: # FIXME type typing + """ + source: https://arrayfire.org/docs/group__c__api__mat.htm#ga0dda6898e1c0d9a43efb56cd6a988c9b + """ + return backend.af_get_type(type_, arr) + + +@safe_call +def af_get_elements(elems: ..., arr: AFArray, /) -> Any: # FIXME elems typing + """ + source: https://arrayfire.org/docs/group__c__api__mat.htm#ga6845bbe4385a60a606b88f8130252c1f + """ + return backend.af_get_elements(elems, arr) + + +@safe_call +def af_get_numdims(result: ..., arr: AFArray, /) -> Any: # FIXME typing + """ + source: https://arrayfire.org/docs/group__c__api__mat.htm#gaefa019d932ff58c2a829ce87edddd2a8 + """ + return backend.af_get_numdims(result, arr) + + +@safe_call +def af_get_dims(d0: ..., d1: ..., d2: ..., d3: ..., arr: AFArray, /) -> Any: # FIXME + """ + source: https://arrayfire.org/docs/group__c__api__mat.htm#ga8b90da50a532837d9763e301b2267348 + """ + return backend.af_get_dims(d0, d1, d2, d3, arr) + + +@safe_call +def af_get_scalar(output_value: ..., arr: AFArray, /) -> Any: # FIXME + """ + source: https://arrayfire.org/docs/group__c__api__mat.htm#gaefe2e343a74a84bd43b588218ecc09a3 + """ + return backend.af_get_scalar(output_value, arr) + + +@safe_call +def af_is_empty(result: ..., arr: AFArray, /) -> Any: + """ + source: https://arrayfire.org/docs/group__c__api__mat.htm#ga19c749e95314e1c77d816ad9952fb680 + """ + return backend.af_is_empty(result, arr) + + +@safe_call +def af_get_data_ptr(data: ..., arr: AFArray, /) -> Any: + """ + source: https://arrayfire.org/docs/group__c__api__mat.htm#ga6040dc6f0eb127402fbf62c1165f0b9d + """ + return backend.af_get_data_ptr(data, arr) + + +# Arrayfire Functions + + +@safe_call +def af_index_gen(out: AFArrayPointer, in_: AFArray, ndims: int, indices: ..., /) -> Any: # FIXME indices + """ + source: https://arrayfire.org/docs/group__index__func__index.htm#ga14a7d149dba0ed0b977335a3df9d91e6 + """ + return backend.af_index_gen(out, in_, ndims, indices) + + +@safe_call +def af_transpose(out: AFArrayPointer, in_: AFArray, conjugate: bool, /) -> Any: + """ + https://arrayfire.org/docs/group__blas__func__transpose.htm#ga716b2b9bf190c8f8d0970aef2b57d8e7 + """ + return backend.af_transpose(out, in_, conjugate) + + +@safe_call +def af_reorder(out: AFArrayPointer, in_: AFArray, x: ..., y: ..., z: ..., w: ..., /) -> Any: # FIXME + """ + source: https://arrayfire.org/docs/group__manip__func__reorder.htm#ga57383f4d00a3a86eab08dddd52c3ad3d + """ + return backend.af_reorder(out, in_, x, y, z, w) + + +@safe_call +def af_array_to_string(output: ..., exp: ..., arr: AFArray, precision: int, transpose: bool, /) -> Any: # FIXME + """ + source: https://arrayfire.org/docs/group__print__func__tostring.htm#ga01f32ef2420b5d4592c6e4b4964b863b + """ + return backend.af_array_to_string(output, exp, arr, precision, transpose) + + +@safe_call +def af_free_host(ptr: ...) -> Any: # FIXME + """ + source: https://arrayfire.org/docs/group__device__func__free__host.htm#ga3f1149a837a7ebbe8002d5d2244e3370 + """ + return backend.af_free_host(ptr) + + +@safe_call +def af_constant_complex(arr: AFArrayPointer, real: ..., imag: ..., ndims: int, dims: ..., type_: ..., /) -> Any: + """ + source: https://arrayfire.org/docs/group__data__func__constant.htm#ga5a083b1f3cd8a72a41f151de3bdea1a2 + """ + return backend.af_constant_complex(arr, real, imag, ndims, dims, type_) + + +@safe_call +def af_constant_long(arr: AFArrayPointer, val: ..., ndims: ..., dims: ..., /) -> Any: + """ + source: https://arrayfire.org/docs/group__data__func__constant.htm#ga10f1c9fad1ce9e9fefd885d5a1d1fd49 + """ + return backend.af_constant_long(arr, val, ndims, dims) + + +@safe_call +def af_constant_ulong(arr: AFArrayPointer, val: ..., ndims: ..., dims: ..., /) -> Any: + """ + source: https://arrayfire.org/docs/group__data__func__constant.htm#ga67af670cc9314589f8134019f5e68809 + """ + return backend.af_constant_ulong(arr, val, ndims, dims) + + +@safe_call +def af_constant(arr: AFArrayPointer, val: ..., ndims: ..., dims: ..., type_: ..., /) -> Any: + """ + source: https://arrayfire.org/docs/group__data__func__constant.htm#gafc51b6a98765dd24cd4139f3bde00670 + """ + return backend.af_constant(arr, val, ndims, dims, type_) + # Arithmetic Operators From 171ec88c2727328c1653ef52cb9f975610cf7d87 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 11 Feb 2023 01:55:28 +0200 Subject: [PATCH 30/36] Refactor backend and project structure --- arrayfire/array_api/array_object.py | 261 ++++--------- arrayfire/array_api/backend/__init__.py | 22 +- arrayfire/array_api/backend/constant_array.py | 84 ++++ arrayfire/array_api/backend/library.py | 364 +++++++++++------- arrayfire/array_api/dtype_functions.py | 30 -- .../{dtypes.py => dtypes/__init__.py} | 30 +- arrayfire/array_api/dtypes/functions.py | 32 ++ arrayfire/array_api/dtypes/helpers.py | 60 +++ arrayfire/array_api/operators.py | 23 +- 9 files changed, 519 insertions(+), 387 deletions(-) create mode 100644 arrayfire/array_api/backend/constant_array.py delete mode 100644 arrayfire/array_api/dtype_functions.py rename arrayfire/array_api/{dtypes.py => dtypes/__init__.py} (55%) create mode 100644 arrayfire/array_api/dtypes/functions.py create mode 100644 arrayfire/array_api/dtypes/helpers.py diff --git a/arrayfire/array_api/array_object.py b/arrayfire/array_api/array_object.py index 0ccf36ee8..46691fe83 100644 --- a/arrayfire/array_api/array_object.py +++ b/arrayfire/array_api/array_object.py @@ -3,50 +3,32 @@ import array as py_array import ctypes import enum -from dataclasses import dataclass from typing import Any, List, Optional, Tuple, Union # TODO replace imports from original lib with refactored ones from arrayfire.algorithm import count from arrayfire.array import _get_indices, _in_display_dims_limit -from .backend import library +from .backend import ArrayBuffer, library +from .backend.constant_array import create_constant_array from .device import PointerSource -from .dtypes import CShape, Dtype +from .dtypes import CType from .dtypes import bool as af_bool -from .dtypes import c_dim_t -from .dtypes import complex64 as af_complex64 -from .dtypes import complex128 as af_complex128 from .dtypes import float32 as af_float32 -from .dtypes import float64 as af_float64 -from .dtypes import int64 as af_int64 -from .dtypes import supported_dtypes, to_str -from .dtypes import uint64 as af_uint64 - -ShapeType = Tuple[int, ...] -# HACK, TODO replace for actual bcast_var after refactoring ~ https://github.com/arrayfire/arrayfire/pull/2871 -_bcast_var = False +from .dtypes import supported_dtypes +from .dtypes.helpers import CShape, Dtype, c_dim_t, to_str # TODO use int | float in operators -> remove bool | complex support -@dataclass -class _ArrayBuffer: - address: Optional[int] = None - length: int = 0 - - class Array: def __init__( self, x: Union[None, Array, py_array.array, int, ctypes.c_void_p, List[Union[int, float]]] = None, - dtype: Union[None, Dtype, str] = None, shape: Optional[ShapeType] = None, - pointer_source: PointerSource = PointerSource.host, offset: Optional[ctypes._SimpleCData[int]] = None, - strides: Optional[ShapeType] = None) -> None: + dtype: Union[None, Dtype, str] = None, shape: Tuple[int, ...] = (), + pointer_source: PointerSource = PointerSource.host, offset: Optional[CType] = None, + strides: Optional[Tuple[int, ...]] = None) -> None: _no_initial_dtype = False # HACK, FIXME - # Initialise array object - self.arr = ctypes.c_void_p(0) - if isinstance(dtype, str): dtype = _str_to_dtype(dtype) # type: ignore[arg-type] @@ -56,30 +38,27 @@ def __init__( if x is None: if not shape: # shape is None or empty tuple - library.af_create_handle( - ctypes.pointer(self.arr), 0, ctypes.pointer(CShape().c_array), dtype.c_api_value) + self.arr = library.create_handle((), dtype) return - # NOTE: applies inplace changes for self.arr - library.af_create_handle( - ctypes.pointer(self.arr), len(shape), ctypes.pointer(CShape(*shape).c_array), dtype.c_api_value) + self.arr = library.create_handle(shape, dtype) return if isinstance(x, Array): - library.af_retain_array(ctypes.pointer(self.arr), x.arr) + self.arr = library.retain_array(x.arr) return if isinstance(x, py_array.array): - _type_char = x.typecode - _array_buffer = _ArrayBuffer(*x.buffer_info()) + _type_char: str = x.typecode + _array_buffer = ArrayBuffer(*x.buffer_info()) elif isinstance(x, list): _array = py_array.array("f", x) # BUG [True, False] -> dtype: f32 # TODO add int and float _type_char = _array.typecode - _array_buffer = _ArrayBuffer(*_array.buffer_info()) + _array_buffer = ArrayBuffer(*_array.buffer_info()) elif isinstance(x, int) or isinstance(x, ctypes.c_void_p): # TODO - _array_buffer = _ArrayBuffer(x if not isinstance(x, ctypes.c_void_p) else x.value) + _array_buffer = ArrayBuffer(x if not isinstance(x, ctypes.c_void_p) else x.value) if not shape: raise TypeError("Expected to receive the initial shape due to the x being a data pointer.") @@ -87,42 +66,29 @@ def __init__( if _no_initial_dtype: raise TypeError("Expected to receive the initial dtype due to the x being a data pointer.") - _type_char = dtype.typecode # type: ignore[assignment] # FIXME + _type_char = dtype.typecode else: raise TypeError("Passed object x is an object of unsupported class.") - _cshape = _get_cshape(shape, _array_buffer.length) + if not shape: + if _array_buffer.length != 0: + shape = (_array_buffer.length, ) + else: + RuntimeError("Shape and buffer length are size invalid.") if not _no_initial_dtype and dtype.typecode != _type_char: raise TypeError("Can not create array of requested type from input data type") if not (offset or strides): if pointer_source == PointerSource.host: - library.af_create_array( - ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), _cshape.original_shape, - ctypes.pointer(_cshape.c_array), dtype.c_api_value) + self.arr = library.create_array(shape, dtype, _array_buffer) return - library.af_device_array( - ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), _cshape.original_shape, - ctypes.pointer(_cshape.c_array), dtype.c_api_value) + self.arr = library.device_array(shape, dtype, _array_buffer) return - if offset is None: - offset = c_dim_t(0) - - if strides is None: - strides = (1, _cshape[0], _cshape[0]*_cshape[1], _cshape[0]*_cshape[1]*_cshape[2]) - - if len(strides) < 4: - strides += (strides[-1], ) * (4 - len(strides)) - strides_cshape = CShape(*strides).c_array - - library.af_create_strided_array( - ctypes.pointer(self.arr), ctypes.c_void_p(_array_buffer.address), offset, _cshape.original_shape, - ctypes.pointer(_cshape.c_array), ctypes.pointer(strides_cshape), dtype.c_api_value, - pointer_source.value) + self.arr = library.create_strided_array(shape, dtype, _array_buffer, offset, strides, pointer_source) # Arithmetic Operators @@ -159,7 +125,7 @@ def __neg__(self) -> Array: determined by Type Promotion Rules. """ - return 0 - self # type: ignore[no-any-return, operator] # FIXME + return _process_c_function(0, self, library.sub) def __add__(self, other: Union[int, float, Array], /) -> Array: """ @@ -178,7 +144,7 @@ def __add__(self, other: Union[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, library.af_add) + return _process_c_function(self, other, library.add) def __sub__(self, other: Union[int, float, Array], /) -> Array: """ @@ -200,7 +166,7 @@ def __sub__(self, other: Union[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, library.af_sub) + return _process_c_function(self, other, library.sub) def __mul__(self, other: Union[int, float, Array], /) -> Array: """ @@ -219,7 +185,7 @@ def __mul__(self, other: Union[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, library.af_mul) + return _process_c_function(self, other, library.mul) def __truediv__(self, other: Union[int, float, Array], /) -> Array: """ @@ -246,7 +212,7 @@ def __truediv__(self, other: Union[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, library.af_div) + return _process_c_function(self, other, library.div) def __floordiv__(self, other: Union[int, float, Array], /) -> Array: # TODO @@ -276,7 +242,7 @@ def __mod__(self, other: Union[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, library.af_mod) + return _process_c_function(self, other, library.mod) def __pow__(self, other: Union[int, float, Array], /) -> Array: """ @@ -298,7 +264,7 @@ def __pow__(self, other: Union[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, library.af_pow) + return _process_c_function(self, other, library.pow) # Array Operators @@ -322,8 +288,9 @@ 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() - library.af_bitnot(ctypes.pointer(out.arr), self.arr) + out.arr = library.bitnot(self.arr) return out def __and__(self, other: Union[int, bool, Array], /) -> Array: @@ -344,7 +311,7 @@ def __and__(self, other: Union[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, library.af_bitand) + return _process_c_function(self, other, library.bitand) def __or__(self, other: Union[int, bool, Array], /) -> Array: """ @@ -364,7 +331,7 @@ def __or__(self, other: Union[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, library.af_bitor) + return _process_c_function(self, other, library.bitor) def __xor__(self, other: Union[int, bool, Array], /) -> Array: """ @@ -384,7 +351,7 @@ def __xor__(self, other: Union[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, library.af_bitxor) + return _process_c_function(self, other, library.bitxor) def __lshift__(self, other: Union[int, Array], /) -> Array: """ @@ -404,7 +371,7 @@ def __lshift__(self, other: Union[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, library.af_bitshiftl) + return _process_c_function(self, other, library.bitshiftl) def __rshift__(self, other: Union[int, Array], /) -> Array: """ @@ -424,7 +391,7 @@ def __rshift__(self, other: Union[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, library.af_bitshiftr) + return _process_c_function(self, other, library.bitshiftr) # Comparison Operators @@ -445,7 +412,7 @@ def __lt__(self, other: Union[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, library.af_lt) + return _process_c_function(self, other, library.lt) def __le__(self, other: Union[int, float, Array], /) -> Array: """ @@ -464,7 +431,7 @@ def __le__(self, other: Union[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, library.af_le) + return _process_c_function(self, other, library.le) def __gt__(self, other: Union[int, float, Array], /) -> Array: """ @@ -483,7 +450,7 @@ def __gt__(self, other: Union[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, library.af_gt) + return _process_c_function(self, other, library.gt) def __ge__(self, other: Union[int, float, Array], /) -> Array: """ @@ -502,9 +469,9 @@ def __ge__(self, other: Union[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, library.af_ge) + return _process_c_function(self, other, library.ge) - def __eq__(self, other: Union[int, float, bool, Array], /) -> Array: + def __eq__(self, other: Union[int, float, bool, Array], /) -> Array: # type: ignore[override] """ Computes the truth value of self_i == other_i for each element of an array instance with the respective element of the array other. @@ -521,9 +488,9 @@ def __eq__(self, other: Union[int, float, bool, 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, library.af_eq) + return _process_c_function(self, other, library.eq) - def __ne__(self, other: Union[int, float, bool, Array], /) -> Array: + def __ne__(self, other: Union[int, float, bool, Array], /) -> Array: # type: ignore[override] """ Computes the truth value of self_i != other_i for each element of an array instance with the respective element of the array other. @@ -540,7 +507,7 @@ def __ne__(self, other: Union[int, float, bool, 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, library.af_neq) + return _process_c_function(self, other, library.neq) # Reflected Arithmetic Operators @@ -548,25 +515,25 @@ def __radd__(self, other: Array, /) -> Array: """ Return other + self. """ - return _process_c_function(other, self, library.af_add) + return _process_c_function(other, self, library.add) def __rsub__(self, other: Array, /) -> Array: """ Return other - self. """ - return _process_c_function(other, self, library.af_sub) + return _process_c_function(other, self, library.sub) def __rmul__(self, other: Array, /) -> Array: """ Return other * self. """ - return _process_c_function(other, self, library.af_mul) + return _process_c_function(other, self, library.mul) def __rtruediv__(self, other: Array, /) -> Array: """ Return other / self. """ - return _process_c_function(other, self, library.af_div) + return _process_c_function(other, self, library.div) def __rfloordiv__(self, other: Array, /) -> Array: # TODO @@ -576,13 +543,13 @@ def __rmod__(self, other: Array, /) -> Array: """ Return other % self. """ - return _process_c_function(other, self, library.af_mod) + return _process_c_function(other, self, library.mod) def __rpow__(self, other: Array, /) -> Array: """ Return other ** self. """ - return _process_c_function(other, self, library.af_pow) + return _process_c_function(other, self, library.pow) # Reflected Array Operators @@ -596,31 +563,31 @@ def __rand__(self, other: Array, /) -> Array: """ Return other & self. """ - return _process_c_function(other, self, library.af_bitand) + return _process_c_function(other, self, library.bitand) def __ror__(self, other: Array, /) -> Array: """ Return other | self. """ - return _process_c_function(other, self, library.af_bitor) + return _process_c_function(other, self, library.bitor) def __rxor__(self, other: Array, /) -> Array: """ Return other ^ self. """ - return _process_c_function(other, self, library.af_bitxor) + return _process_c_function(other, self, library.bitxor) def __rlshift__(self, other: Array, /) -> Array: """ Return other << self. """ - return _process_c_function(other, self, library.af_bitshiftl) + return _process_c_function(other, self, library.bitshiftl) def __rrshift__(self, other: Array, /) -> Array: """ Return other >> self. """ - return _process_c_function(other, self, library.af_bitshiftr) + return _process_c_function(other, self, library.bitshiftr) # In-place Arithmetic Operators @@ -629,25 +596,25 @@ def __iadd__(self, other: Union[int, float, Array], /) -> Array: """ Return self += other. """ - return _process_c_function(self, other, library.af_add) + return _process_c_function(self, other, library.add) def __isub__(self, other: Union[int, float, Array], /) -> Array: """ Return self -= other. """ - return _process_c_function(self, other, library.af_sub) + return _process_c_function(self, other, library.sub) def __imul__(self, other: Union[int, float, Array], /) -> Array: """ Return self *= other. """ - return _process_c_function(self, other, library.af_mul) + return _process_c_function(self, other, library.mul) def __itruediv__(self, other: Union[int, float, Array], /) -> Array: """ Return self /= other. """ - return _process_c_function(self, other, library.af_div) + return _process_c_function(self, other, library.div) def __ifloordiv__(self, other: Union[int, float, Array], /) -> Array: # TODO @@ -657,13 +624,13 @@ def __imod__(self, other: Union[int, float, Array], /) -> Array: """ Return self %= other. """ - return _process_c_function(self, other, library.af_mod) + return _process_c_function(self, other, library.mod) def __ipow__(self, other: Union[int, float, Array], /) -> Array: """ Return self **= other. """ - return _process_c_function(self, other, library.af_pow) + return _process_c_function(self, other, library.pow) # In-place Array Operators @@ -677,31 +644,31 @@ def __iand__(self, other: Union[int, bool, Array], /) -> Array: """ Return self &= other. """ - return _process_c_function(self, other, library.af_bitand) + return _process_c_function(self, other, library.bitand) def __ior__(self, other: Union[int, bool, Array], /) -> Array: """ Return self |= other. """ - return _process_c_function(self, other, library.af_bitor) + return _process_c_function(self, other, library.bitor) def __ixor__(self, other: Union[int, bool, Array], /) -> Array: """ Return self ^= other. """ - return _process_c_function(self, other, library.af_bitxor) + return _process_c_function(self, other, library.bitxor) def __ilshift__(self, other: Union[int, Array], /) -> Array: """ Return self <<= other. """ - return _process_c_function(self, other, library.af_bitshiftl) + return _process_c_function(self, other, library.bitshiftl) def __irshift__(self, other: Union[int, Array], /) -> Array: """ Return self >>= other. """ - return _process_c_function(self, other, library.af_bitshiftr) + return _process_c_function(self, other, library.bitshiftr) # Methods @@ -808,9 +775,8 @@ def dtype(self) -> Dtype: out : Dtype Array data type. """ - out = ctypes.c_int() - library.af_get_type(ctypes.pointer(out), self.arr) - return _c_api_value_to_dtype(out.value) + ctype = library.get_ctype(self.arr) + return _c_api_value_to_dtype(ctype) @property def device(self) -> Any: @@ -861,9 +827,7 @@ def size(self) -> int: - This must equal the product of the array's dimensions. """ # NOTE previously - elements() - out = c_dim_t(0) - library.af_get_elements(ctypes.pointer(out), self.arr) - return out.value + return library.get_elements(self.arr) @property def ndim(self) -> int: @@ -873,12 +837,10 @@ def ndim(self) -> int: out : int Number of array dimensions (axes). """ - out = ctypes.c_uint(0) - library.af_get_numdims(ctypes.pointer(out), self.arr) - return out.value + return library.get_numdims(self.arr) @property - def shape(self) -> ShapeType: + def shape(self) -> Tuple[int, ...]: """ Array dimensions. @@ -973,23 +935,13 @@ def _array_as_str(array: Array) -> str: return py_str -def _metadata_string(dtype: Dtype, dims: Optional[ShapeType] = None) -> str: +def _metadata_string(dtype: Dtype, dims: Optional[Tuple[int, ...]] = None) -> str: return ( "arrayfire.Array()\n" f"Type: {dtype.typename}\n" f"Dims: {str(dims) if dims else ''}") -def _get_cshape(shape: Optional[ShapeType], buffer_length: int) -> CShape: - if shape: - return CShape(*shape) - - if buffer_length != 0: - return CShape(buffer_length) - - raise RuntimeError("Shape and buffer length are size invalid.") - - def _c_api_value_to_dtype(value: int) -> Dtype: for dtype in supported_dtypes: if value == dtype.c_api_value: @@ -1006,9 +958,7 @@ def _str_to_dtype(value: int) -> Dtype: raise TypeError("There is no supported dtype that matches passed dtype typecode.") -def _process_c_function( - lhs: Union[int, float, Array], rhs: Union[int, float, Array], - c_function: Any) -> Array: +def _process_c_function(lhs: Union[int, float, Array], rhs: Union[int, float, Array], c_function: Any) -> Array: out = Array() if isinstance(lhs, Array) and isinstance(rhs, Array): @@ -1016,70 +966,15 @@ def _process_c_function( rhs_array = rhs.arr elif isinstance(lhs, Array) and isinstance(rhs, (int, float)): - rhs_dtype = _implicit_dtype(rhs, lhs.dtype) - rhs_constant_array = _constant_array(rhs, CShape(*lhs.shape), rhs_dtype) - lhs_array = lhs.arr - rhs_array = rhs_constant_array.arr + rhs_array = create_constant_array(rhs, lhs.shape, lhs.dtype) elif isinstance(lhs, (int, float)) and isinstance(rhs, Array): - lhs_dtype = _implicit_dtype(lhs, rhs.dtype) - lhs_constant_array = _constant_array(lhs, CShape(*rhs.shape), lhs_dtype) - - lhs_array = lhs_constant_array.arr + lhs_array = create_constant_array(lhs, rhs.shape, rhs.dtype) rhs_array = rhs.arr else: raise TypeError(f"{type(rhs)} is not supported and can not be passed to C binary function.") - c_function(ctypes.pointer(out.arr), lhs_array, rhs_array, _bcast_var) - - return out - - -def _implicit_dtype(value: Union[int, float], array_dtype: Dtype) -> Dtype: - if isinstance(value, bool): - value_dtype = af_bool - if isinstance(value, int): - value_dtype = af_int64 - elif isinstance(value, float): - value_dtype = af_float64 - elif isinstance(value, complex): - value_dtype = af_complex128 - else: - raise TypeError(f"{type(value)} is not supported and can not be converted to af.Dtype.") - - if not (array_dtype == af_float32 or array_dtype == af_complex64): - return value_dtype - - if value_dtype == af_float64: - return af_float32 - - if value_dtype == af_complex128: - return af_complex64 - - return value_dtype - - -def _constant_array(value: Union[int, float], cshape: CShape, dtype: Dtype) -> Array: - out = Array() - - if isinstance(value, complex): - if dtype != af_complex64 and dtype != af_complex128: - dtype = af_complex64 - - library.af_constant_complex( - ctypes.pointer(out.arr), ctypes.c_double(value.real), ctypes.c_double(value.imag), 4, - ctypes.pointer(cshape.c_array), dtype.c_api_value) - elif dtype == af_int64: - # TODO discuss workaround for passing float to ctypes - library.af_constant_long( - ctypes.pointer(out.arr), af_int64.c_type(value.real), 4, ctypes.pointer(cshape.c_array)) - elif dtype == af_uint64: - library.af_constant_ulong( - ctypes.pointer(out.arr), af_uint64.c_type(value.real), 4, ctypes.pointer(cshape.c_array)) - else: - library.af_constant( - ctypes.pointer(out.arr), ctypes.c_double(value), 4, ctypes.pointer(cshape.c_array), dtype.c_api_value) - + out.arr = c_function(lhs_array, rhs_array) return out diff --git a/arrayfire/array_api/backend/__init__.py b/arrayfire/array_api/backend/__init__.py index 26110e3a1..053c20d5e 100644 --- a/arrayfire/array_api/backend/__init__.py +++ b/arrayfire/array_api/backend/__init__.py @@ -1,10 +1,11 @@ import ctypes +from dataclasses import dataclass from typing import Any, Callable -from ..dtypes import c_dim_t, to_str +from ..dtypes.helpers import c_dim_t, to_str from .constants import ErrorCodes -backend = ctypes.CDLL("/opt/arrayfire//lib/libafcpu.3.dylib") # Mock +backend_api = ctypes.CDLL("/opt/arrayfire//lib/libafcpu.3.dylib") # Mock class safe_call: @@ -17,5 +18,20 @@ def __call__(self, *args: Any) -> None: return err_str = ctypes.c_char_p(0) - backend.af_get_last_error(ctypes.pointer(err_str), ctypes.pointer(c_dim_t(0))) + backend_api.af_get_last_error(ctypes.pointer(err_str), ctypes.pointer(c_dim_t(0))) raise RuntimeError(to_str(err_str)) + + +def safe_call_func(c_err): + if c_err == ErrorCodes.none.value: + return + + err_str = ctypes.c_char_p(0) + backend_api.af_get_last_error(ctypes.pointer(err_str), ctypes.pointer(c_dim_t(0))) + raise RuntimeError(to_str(err_str)) + + +@dataclass +class ArrayBuffer: + address: int + length: int = 0 diff --git a/arrayfire/array_api/backend/constant_array.py b/arrayfire/array_api/backend/constant_array.py new file mode 100644 index 000000000..daf58b78c --- /dev/null +++ b/arrayfire/array_api/backend/constant_array.py @@ -0,0 +1,84 @@ +import ctypes +from typing import Tuple, Union + +from ..dtypes import Dtype, int64, uint64 +from ..dtypes.helpers import CShape, implicit_dtype +from . import backend_api, safe_call_func + +AFArray = ctypes.c_void_p + + +def _constant_complex(number: Union[int, float], shape: Tuple[int, ...], dtype: Dtype, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__data__func__constant.htm#ga5a083b1f3cd8a72a41f151de3bdea1a2 + """ + out = ctypes.c_void_p(0) + c_shape = CShape(*shape) + + safe_call_func( + backend_api.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: Union[int, float], shape: Tuple[int, ...], dtype: Dtype, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__data__func__constant.htm#ga10f1c9fad1ce9e9fefd885d5a1d1fd49 + """ + out = ctypes.c_void_p(0) + c_shape = CShape(*shape) + + safe_call_func( + backend_api.af_constant_long( + ctypes.pointer(out), ctypes.c_longlong(number.real), 4, ctypes.pointer(c_shape.c_array)) + ) + return out + + +def _constant_ulong(number: Union[int, float], shape: Tuple[int, ...], dtype: Dtype, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__data__func__constant.htm#ga67af670cc9314589f8134019f5e68809 + """ + # return backend_api.af_constant_ulong(arr, val, ndims, dims) + out = ctypes.c_void_p(0) + c_shape = CShape(*shape) + + safe_call_func( + backend_api.af_constant_ulong( + ctypes.pointer(out), ctypes.c_ulonglong(number.real), 4, ctypes.pointer(c_shape.c_array)) + ) + return out + + +def _constant(number: Union[int, float], shape: Tuple[int, ...], dtype: Dtype, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__data__func__constant.htm#gafc51b6a98765dd24cd4139f3bde00670 + """ + out = ctypes.c_void_p(0) + c_shape = CShape(*shape) + + safe_call_func( + backend_api.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: Union[int, float], shape: Tuple[int, ...], dtype: Dtype, /) -> AFArray: + dtype = implicit_dtype(number, dtype) + + # NOTE complex is not supported in Data API + # if isinstance(number, complex): + # if dtype != complex64 and dtype != complex128: + # dtype = complex64 + # return _constant_complex(number, shape, dtype) + + 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/array_api/backend/library.py b/arrayfire/array_api/backend/library.py index 0285f72bd..4a1e27d75 100644 --- a/arrayfire/array_api/backend/library.py +++ b/arrayfire/array_api/backend/library.py @@ -1,126 +1,179 @@ import ctypes -from typing import Any, Type +from typing import Any, Tuple -from ..dtypes import c_dim_t -from . import backend, safe_call +from ..device import PointerSource +from ..dtypes import CType, Dtype +from ..dtypes.helpers import CShape, c_dim_t +from . import ArrayBuffer, backend_api, safe_call, safe_call_func AFArrayPointer = ctypes._Pointer AFArray = ctypes.c_void_p -Dim_T = Type[c_dim_t] -Pointer = ctypes._Pointer + +# HACK, TODO replace for actual bcast_var after refactoring ~ https://github.com/arrayfire/arrayfire/pull/2871 +_bcast_var = False # Array management -@safe_call -def af_create_handle(arr: AFArrayPointer, ndims: int, dims: Any, type_: int, /) -> Any: # FIXME dims typing +def create_handle(shape: Tuple[int, ...], dtype: Dtype, /) -> AFArray: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga3b8f5cf6fce69aa1574544bc2d44d7d0 """ - return backend.af_create_handle(arr, ndims, dims, type_) + out = ctypes.c_void_p(0) + c_shape = CShape(*shape) + safe_call_func( + backend_api.af_create_handle( + ctypes.pointer(out), c_shape.original_shape, ctypes.pointer(c_shape.c_array), dtype.c_api_value) + ) + return out -@safe_call -def af_retain_array(out: AFArrayPointer, in_: AFArray, /) -> Any: + +def retain_array(arr: AFArray) -> AFArray: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga7ed45b3f881c0f6c80c5cf2af886dbab """ - return backend.af_retain_array(out, in_) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_retain_array(ctypes.pointer(out), arr) + ) + return out -@safe_call -def af_create_array( - arr: AFArrayPointer, data: ctypes.c_void_p, ndims: int, dims: ..., type_: int, /) -> Any: # FIXME dims typing + +def create_array(shape: Tuple[int, ...], dtype: Dtype, array_buffer: ArrayBuffer, /) -> AFArray: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga834be32357616d8ab735087c6f681858 """ - return backend.af_create_array(arr, data, ndims, dims, type_) + out = ctypes.c_void_p(0) + c_shape = CShape(*shape) + safe_call_func( + backend_api.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 -@safe_call -def af_device_array( - arr: AFArrayPointer, data: ctypes.c_void_p, ndims: int, dims: ..., type_: int, /) -> Any: # FIXME dims typing + +def device_array(shape: Tuple[int, ...], dtype: Dtype, array_buffer: ArrayBuffer, /) -> AFArray: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#gaad4fc77f872217e7337cb53bfb623cf5 """ - return backend.af_device_array(arr, data, ndims, dims, type_) + out = ctypes.c_void_p(0) + c_shape = CShape(*shape) + safe_call_func( + backend_api.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 -@safe_call -def af_create_strided_array( - arr: AFArrayPointer, data: ctypes.c_void_p, offset: Dim_T, ndims: int, dims: ..., strides: ..., - ty: int, location: int, /) -> Any: # FIXME dims, strides typing + +def create_strided_array( + shape: Tuple[int, ...], dtype: Dtype, array_buffer: ArrayBuffer, offset: CType, strides: Tuple[int, ...], + pointer_source: PointerSource, /) -> AFArray: """ source: https://arrayfire.org/docs/group__internal__func__create.htm#gad31241a3437b7b8bc3cf49f85e5c4e0c """ - return backend.af_create_strided_array(arr, data, offset, ndims, dims, strides, ty, location) + out = ctypes.c_void_p(0) + c_shape = CShape(*shape) + if offset is None: + offset = c_dim_t(0) -@safe_call -def af_get_type(type_: ..., arr: AFArray, /) -> Any: # FIXME type typing + 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_func( + backend_api.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: AFArray) -> int: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga0dda6898e1c0d9a43efb56cd6a988c9b """ - return backend.af_get_type(type_, arr) + out = ctypes.c_int() + safe_call_func( + backend_api.af_get_type(ctypes.pointer(out), arr) + ) + return out.value -@safe_call -def af_get_elements(elems: ..., arr: AFArray, /) -> Any: # FIXME elems typing + +def get_elements(arr: AFArray) -> int: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga6845bbe4385a60a606b88f8130252c1f """ - return backend.af_get_elements(elems, arr) + out = c_dim_t(0) + safe_call_func( + backend_api.af_get_elements(ctypes.pointer(out), arr) + ) + return out.value -@safe_call -def af_get_numdims(result: ..., arr: AFArray, /) -> Any: # FIXME typing + +def get_numdims(arr: AFArray) -> int: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#gaefa019d932ff58c2a829ce87edddd2a8 """ - return backend.af_get_numdims(result, arr) + out = ctypes.c_uint(0) + + safe_call_func( + backend_api.af_get_numdims(ctypes.pointer(out), arr) + ) + return out.value @safe_call -def af_get_dims(d0: ..., d1: ..., d2: ..., d3: ..., arr: AFArray, /) -> Any: # FIXME +def af_get_dims(d0, d1, d2, d3, arr: AFArray, /) -> Any: # FIXME """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga8b90da50a532837d9763e301b2267348 """ - return backend.af_get_dims(d0, d1, d2, d3, arr) + return backend_api.af_get_dims(d0, d1, d2, d3, arr) @safe_call -def af_get_scalar(output_value: ..., arr: AFArray, /) -> Any: # FIXME +def af_get_scalar(output_value, arr: AFArray, /) -> Any: # FIXME """ source: https://arrayfire.org/docs/group__c__api__mat.htm#gaefe2e343a74a84bd43b588218ecc09a3 """ - return backend.af_get_scalar(output_value, arr) + return backend_api.af_get_scalar(output_value, arr) @safe_call -def af_is_empty(result: ..., arr: AFArray, /) -> Any: +def af_is_empty(result, arr: AFArray, /) -> Any: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga19c749e95314e1c77d816ad9952fb680 """ - return backend.af_is_empty(result, arr) + return backend_api.af_is_empty(result, arr) @safe_call -def af_get_data_ptr(data: ..., arr: AFArray, /) -> Any: +def af_get_data_ptr(data, arr: AFArray, /) -> Any: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga6040dc6f0eb127402fbf62c1165f0b9d """ - return backend.af_get_data_ptr(data, arr) + return backend_api.af_get_data_ptr(data, arr) # Arrayfire Functions @safe_call -def af_index_gen(out: AFArrayPointer, in_: AFArray, ndims: int, indices: ..., /) -> Any: # FIXME indices +def af_index_gen(out: AFArrayPointer, in_: AFArray, ndims: int, indices, /) -> Any: # FIXME indices """ source: https://arrayfire.org/docs/group__index__func__index.htm#ga14a7d149dba0ed0b977335a3df9d91e6 """ - return backend.af_index_gen(out, in_, ndims, indices) + return backend_api.af_index_gen(out, in_, ndims, indices) @safe_call @@ -128,208 +181,247 @@ def af_transpose(out: AFArrayPointer, in_: AFArray, conjugate: bool, /) -> Any: """ https://arrayfire.org/docs/group__blas__func__transpose.htm#ga716b2b9bf190c8f8d0970aef2b57d8e7 """ - return backend.af_transpose(out, in_, conjugate) + return backend_api.af_transpose(out, in_, conjugate) @safe_call -def af_reorder(out: AFArrayPointer, in_: AFArray, x: ..., y: ..., z: ..., w: ..., /) -> Any: # FIXME +def af_reorder(out: AFArrayPointer, in_: AFArray, x, y, z, w, /) -> Any: # FIXME """ source: https://arrayfire.org/docs/group__manip__func__reorder.htm#ga57383f4d00a3a86eab08dddd52c3ad3d """ - return backend.af_reorder(out, in_, x, y, z, w) + return backend_api.af_reorder(out, in_, x, y, z, w) @safe_call -def af_array_to_string(output: ..., exp: ..., arr: AFArray, precision: int, transpose: bool, /) -> Any: # FIXME +def af_array_to_string(output, exp, arr: AFArray, precision: int, transpose: bool, /) -> Any: # FIXME """ source: https://arrayfire.org/docs/group__print__func__tostring.htm#ga01f32ef2420b5d4592c6e4b4964b863b """ - return backend.af_array_to_string(output, exp, arr, precision, transpose) + return backend_api.af_array_to_string(output, exp, arr, precision, transpose) @safe_call -def af_free_host(ptr: ...) -> Any: # FIXME +def af_free_host(ptr) -> Any: # FIXME """ source: https://arrayfire.org/docs/group__device__func__free__host.htm#ga3f1149a837a7ebbe8002d5d2244e3370 """ - return backend.af_free_host(ptr) - - -@safe_call -def af_constant_complex(arr: AFArrayPointer, real: ..., imag: ..., ndims: int, dims: ..., type_: ..., /) -> Any: - """ - source: https://arrayfire.org/docs/group__data__func__constant.htm#ga5a083b1f3cd8a72a41f151de3bdea1a2 - """ - return backend.af_constant_complex(arr, real, imag, ndims, dims, type_) - - -@safe_call -def af_constant_long(arr: AFArrayPointer, val: ..., ndims: ..., dims: ..., /) -> Any: - """ - source: https://arrayfire.org/docs/group__data__func__constant.htm#ga10f1c9fad1ce9e9fefd885d5a1d1fd49 - """ - return backend.af_constant_long(arr, val, ndims, dims) - - -@safe_call -def af_constant_ulong(arr: AFArrayPointer, val: ..., ndims: ..., dims: ..., /) -> Any: - """ - source: https://arrayfire.org/docs/group__data__func__constant.htm#ga67af670cc9314589f8134019f5e68809 - """ - return backend.af_constant_ulong(arr, val, ndims, dims) - - -@safe_call -def af_constant(arr: AFArrayPointer, val: ..., ndims: ..., dims: ..., type_: ..., /) -> Any: - """ - source: https://arrayfire.org/docs/group__data__func__constant.htm#gafc51b6a98765dd24cd4139f3bde00670 - """ - return backend.af_constant(arr, val, ndims, dims, type_) + return backend_api.af_free_host(ptr) # Arithmetic Operators -@safe_call -def af_add(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: +def add(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__add.htm#ga1dfbee755fedd680f4476803ddfe06a7 """ - return backend.af_add(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_add(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_sub(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def sub(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__sub.htm#ga80ff99a2e186c23614ea9f36ffc6f0a4 """ - return backend.af_sub(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_sub(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_mul(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def mul(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__mul.htm#ga5f7588b2809ff7551d38b6a0bd583a02 """ - return backend.af_mul(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_mul(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_div(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def div(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__div.htm#ga21f3f97755702692ec8976934e75fde6 """ - return backend.af_div(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_div(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_mod(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def mod(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__mod.htm#ga01924d1b59d8886e46fabd2dc9b27e0f """ - return backend.af_mod(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_mod(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_pow(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def pow(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__pow.htm#ga0f28be1a9c8b176a78c4a47f483e7fc6 """ - return backend.af_pow(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + + safe_call_func( + backend_api.af_pow(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out # Bitwise Operators -@safe_call -def af_bitnot(out: AFArrayPointer, arr: AFArray, /) -> Any: +def bitnot(arr: AFArray, /) -> Any: """ source: https://arrayfire.org/docs/group__arith__func__bitnot.htm#gaf97e8a38aab59ed2d3a742515467d01e """ - return backend.af_bitnot(out, arr) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_pow(ctypes.pointer(out), arr) + ) + return out -@safe_call -def af_bitand(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: +def bitand(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__bitand.htm#ga45c0779ade4703708596df11cca98800 """ - return backend.af_bitand(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_bitand(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_bitor(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def bitor(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__bitor.htm#ga84c99f77d1d83fd53f949b4d67b5b210 """ - return backend.af_bitor(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_bitor(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_bitxor(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def bitxor(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__bitxor.htm#ga8188620da6b432998e55fdd1fad22100 """ - return backend.af_bitxor(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_bitxor(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_bitshiftl(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def bitshiftl(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__shiftl.htm#ga3139645aafe6f045a5cab454e9c13137 """ - return backend.af_bitshiftl(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_bitshiftl(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_bitshiftr(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def bitshiftr(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__shiftr.htm#ga4c06b9977ecf96cdfc83b5dfd1ac4895 """ - return backend.af_bitshiftr(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_bitshiftr(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_lt(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def lt(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/arith_8h.htm#ae7aa04bf23b32bb11c4bab8bdd637103 """ - return backend.af_lt(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_lt(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_le(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def le(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__le.htm#gad5535ce64dbed46d0773fd494e84e922 """ - return backend.af_le(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_le(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_gt(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def gt(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__gt.htm#ga4e65603259515de8939899a163ebaf9e """ - return backend.af_gt(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_gt(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_ge(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def ge(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__ge.htm#ga4513f212e0b0a22dcf4653e89c85e3d9 """ - return backend.af_ge(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_ge(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_eq(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def eq(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__eq.htm#ga76d2da7716831616bb81effa9e163693 """ - return backend.af_eq(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + safe_call_func( + backend_api.af_eq(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out -@safe_call -def af_neq(out: AFArrayPointer, lhs: AFArray, rhs: AFArray, batch: bool, /) -> Any: + +def neq(lhs: AFArray, rhs: AFArray, /) -> AFArray: """ source: https://arrayfire.org/docs/group__arith__func__neq.htm#gae4ee8bd06a410f259f1493fb811ce441 """ - return backend.af_neq(out, lhs, rhs, batch) + out = ctypes.c_void_p(0) + + safe_call_func( + backend_api.af_neq(ctypes.pointer(out), lhs, rhs, _bcast_var) + ) + return out diff --git a/arrayfire/array_api/dtype_functions.py b/arrayfire/array_api/dtype_functions.py deleted file mode 100644 index 905d1ba97..000000000 --- a/arrayfire/array_api/dtype_functions.py +++ /dev/null @@ -1,30 +0,0 @@ -from .array_object import Array -from .dtypes import Dtype - -# TODO implement functions - - -def astype(x: Array, dtype: Dtype, /, *, copy: bool = True) -> Array: - return NotImplemented - - -def can_cast(from_: Dtype | Array, to: Dtype, /) -> bool: - return NotImplemented - - -def finfo(type: Dtype | Array, /): # type: ignore[no-untyped-def] - # NOTE expected return type -> finfo_object - return NotImplemented - - -def iinfo(type: Dtype | Array, /): # type: ignore[no-untyped-def] - # NOTE expected return type -> iinfo_object - return NotImplemented - - -def isdtype(dtype: Dtype, kind: Dtype | str | tuple[Dtype | str, ...]) -> bool: - return NotImplemented - - -def result_type(*arrays_and_dtypes: Dtype | Array) -> Dtype: - return NotImplemented diff --git a/arrayfire/array_api/dtypes.py b/arrayfire/array_api/dtypes/__init__.py similarity index 55% rename from arrayfire/array_api/dtypes.py rename to arrayfire/array_api/dtypes/__init__.py index bd77c546c..d0d015603 100644 --- a/arrayfire/array_api/dtypes.py +++ b/arrayfire/array_api/dtypes/__init__.py @@ -4,15 +4,13 @@ from dataclasses import dataclass from typing import Type -from .config import is_arch_x86 - -c_dim_t = ctypes.c_int if is_arch_x86() else ctypes.c_longlong +CType = Type[ctypes._SimpleCData] @dataclass class Dtype: typecode: str - c_type: Type[ctypes._SimpleCData] + c_type: CType typename: str c_api_value: int # Internal use only @@ -35,27 +33,3 @@ class Dtype: supported_dtypes = [ int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128, bool ] - - -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] diff --git a/arrayfire/array_api/dtypes/functions.py b/arrayfire/array_api/dtypes/functions.py new file mode 100644 index 000000000..155dc61d5 --- /dev/null +++ b/arrayfire/array_api/dtypes/functions.py @@ -0,0 +1,32 @@ +from typing import Tuple, Union + +from ..array_object import Array +from . import Dtype + +# TODO implement functions + + +def astype(x: Array, dtype: Dtype, /, *, copy: bool = True) -> Array: + return NotImplemented + + +def can_cast(from_: Union[Dtype, Array], to: Dtype, /) -> bool: + return NotImplemented + + +def finfo(type: Union[Dtype, Array], /): # type: ignore[no-untyped-def] + # NOTE expected return type -> finfo_object + return NotImplemented + + +def iinfo(type: Union[Dtype, Array], /): # type: ignore[no-untyped-def] + # NOTE expected return type -> iinfo_object + return NotImplemented + + +def isdtype(dtype: Dtype, kind: Union[Dtype, str, Tuple[Union[Dtype, str], ...]]) -> bool: + return NotImplemented + + +def result_type(*arrays_and_dtypes: Union[Dtype, Array]) -> Dtype: + return NotImplemented diff --git a/arrayfire/array_api/dtypes/helpers.py b/arrayfire/array_api/dtypes/helpers.py new file mode 100644 index 000000000..7586c3bce --- /dev/null +++ b/arrayfire/array_api/dtypes/helpers.py @@ -0,0 +1,60 @@ +from __future__ import annotations + +import ctypes +from typing import Tuple, Union + +from ..config import is_arch_x86 +from . import Dtype +from . import bool as af_bool +from . import complex64, complex128, float32, float64, int64 + +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: Union[int, float], array_dtype: Dtype) -> Dtype: + if isinstance(number, bool): + number_dtype = af_bool + if isinstance(number, int): + number_dtype = int64 + elif isinstance(number, float): + number_dtype = float64 + elif isinstance(number, complex): + number_dtype = complex128 + 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): + return number_dtype + + if number_dtype == float64: + return float32 + + if number_dtype == complex128: + return complex64 + + return number_dtype diff --git a/arrayfire/array_api/operators.py b/arrayfire/array_api/operators.py index 9447d3fa7..8cc6f8b56 100644 --- a/arrayfire/array_api/operators.py +++ b/arrayfire/array_api/operators.py @@ -1,16 +1,25 @@ -import ctypes +from typing import Callable from .array_object import Array from .backend import library +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 add(x1: Array, x2: Array, /) -> Array: - out = Array() - library.af_add(ctypes.pointer(out.arr), x1.arr, x2.arr, False) - return out + return library.add(x1, x2) +@return_copy def sub(x1: Array, x2: Array, /) -> Array: - out = Array() - library.af_sub(ctypes.pointer(out.arr), x1.arr, x2.arr, False) - return out + return library.sub(x1, x2) From e984caaef7cb9a08e66bb75bdccccb6d0d4cf94f Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 11 Feb 2023 02:38:26 +0200 Subject: [PATCH 31/36] Refactor backend library operators --- arrayfire/array_api/array_object.py | 83 +++--- arrayfire/array_api/backend/__init__.py | 47 +--- arrayfire/array_api/backend/backend.py | 26 ++ arrayfire/array_api/backend/constant_array.py | 10 +- arrayfire/array_api/backend/constants.py | 38 --- arrayfire/array_api/backend/library.py | 246 +----------------- arrayfire/array_api/backend/operators.py | 149 +++++++++++ arrayfire/array_api/operators.py | 6 +- 8 files changed, 244 insertions(+), 361 deletions(-) create mode 100644 arrayfire/array_api/backend/backend.py delete mode 100644 arrayfire/array_api/backend/constants.py create mode 100644 arrayfire/array_api/backend/operators.py diff --git a/arrayfire/array_api/array_object.py b/arrayfire/array_api/array_object.py index 46691fe83..de9e7092a 100644 --- a/arrayfire/array_api/array_object.py +++ b/arrayfire/array_api/array_object.py @@ -9,6 +9,7 @@ from arrayfire.algorithm import count from arrayfire.array import _get_indices, _in_display_dims_limit +from . import backend from .backend import ArrayBuffer, library from .backend.constant_array import create_constant_array from .device import PointerSource @@ -125,7 +126,7 @@ def __neg__(self) -> Array: determined by Type Promotion Rules. """ - return _process_c_function(0, self, library.sub) + return _process_c_function(0, self, backend.sub) def __add__(self, other: Union[int, float, Array], /) -> Array: """ @@ -144,7 +145,7 @@ def __add__(self, other: Union[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, library.add) + return _process_c_function(self, other, backend.add) def __sub__(self, other: Union[int, float, Array], /) -> Array: """ @@ -166,7 +167,7 @@ def __sub__(self, other: Union[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, library.sub) + return _process_c_function(self, other, backend.sub) def __mul__(self, other: Union[int, float, Array], /) -> Array: """ @@ -185,7 +186,7 @@ def __mul__(self, other: Union[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, library.mul) + return _process_c_function(self, other, backend.mul) def __truediv__(self, other: Union[int, float, Array], /) -> Array: """ @@ -212,7 +213,7 @@ def __truediv__(self, other: Union[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, library.div) + return _process_c_function(self, other, backend.div) def __floordiv__(self, other: Union[int, float, Array], /) -> Array: # TODO @@ -242,7 +243,7 @@ def __mod__(self, other: Union[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, library.mod) + return _process_c_function(self, other, backend.mod) def __pow__(self, other: Union[int, float, Array], /) -> Array: """ @@ -264,7 +265,7 @@ def __pow__(self, other: Union[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, library.pow) + return _process_c_function(self, other, backend.pow) # Array Operators @@ -290,7 +291,7 @@ def __invert__(self) -> Array: """ # FIXME out = Array() - out.arr = library.bitnot(self.arr) + out.arr = backend.bitnot(self.arr) return out def __and__(self, other: Union[int, bool, Array], /) -> Array: @@ -311,7 +312,7 @@ def __and__(self, other: Union[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, library.bitand) + return _process_c_function(self, other, backend.bitand) def __or__(self, other: Union[int, bool, Array], /) -> Array: """ @@ -331,7 +332,7 @@ def __or__(self, other: Union[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, library.bitor) + return _process_c_function(self, other, backend.bitor) def __xor__(self, other: Union[int, bool, Array], /) -> Array: """ @@ -351,7 +352,7 @@ def __xor__(self, other: Union[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, library.bitxor) + return _process_c_function(self, other, backend.bitxor) def __lshift__(self, other: Union[int, Array], /) -> Array: """ @@ -371,7 +372,7 @@ def __lshift__(self, other: Union[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, library.bitshiftl) + return _process_c_function(self, other, backend.bitshiftl) def __rshift__(self, other: Union[int, Array], /) -> Array: """ @@ -391,7 +392,7 @@ def __rshift__(self, other: Union[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, library.bitshiftr) + return _process_c_function(self, other, backend.bitshiftr) # Comparison Operators @@ -412,7 +413,7 @@ def __lt__(self, other: Union[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, library.lt) + return _process_c_function(self, other, backend.lt) def __le__(self, other: Union[int, float, Array], /) -> Array: """ @@ -431,7 +432,7 @@ def __le__(self, other: Union[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, library.le) + return _process_c_function(self, other, backend.le) def __gt__(self, other: Union[int, float, Array], /) -> Array: """ @@ -450,7 +451,7 @@ def __gt__(self, other: Union[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, library.gt) + return _process_c_function(self, other, backend.gt) def __ge__(self, other: Union[int, float, Array], /) -> Array: """ @@ -469,7 +470,7 @@ def __ge__(self, other: Union[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, library.ge) + return _process_c_function(self, other, backend.ge) def __eq__(self, other: Union[int, float, bool, Array], /) -> Array: # type: ignore[override] """ @@ -488,7 +489,7 @@ def __eq__(self, other: Union[int, float, bool, Array], /) -> Array: # type: ig 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, library.eq) + return _process_c_function(self, other, backend.eq) def __ne__(self, other: Union[int, float, bool, Array], /) -> Array: # type: ignore[override] """ @@ -507,7 +508,7 @@ def __ne__(self, other: Union[int, float, bool, Array], /) -> Array: # type: ig 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, library.neq) + return _process_c_function(self, other, backend.neq) # Reflected Arithmetic Operators @@ -515,25 +516,25 @@ def __radd__(self, other: Array, /) -> Array: """ Return other + self. """ - return _process_c_function(other, self, library.add) + return _process_c_function(other, self, backend.add) def __rsub__(self, other: Array, /) -> Array: """ Return other - self. """ - return _process_c_function(other, self, library.sub) + return _process_c_function(other, self, backend.sub) def __rmul__(self, other: Array, /) -> Array: """ Return other * self. """ - return _process_c_function(other, self, library.mul) + return _process_c_function(other, self, backend.mul) def __rtruediv__(self, other: Array, /) -> Array: """ Return other / self. """ - return _process_c_function(other, self, library.div) + return _process_c_function(other, self, backend.div) def __rfloordiv__(self, other: Array, /) -> Array: # TODO @@ -543,13 +544,13 @@ def __rmod__(self, other: Array, /) -> Array: """ Return other % self. """ - return _process_c_function(other, self, library.mod) + return _process_c_function(other, self, backend.mod) def __rpow__(self, other: Array, /) -> Array: """ Return other ** self. """ - return _process_c_function(other, self, library.pow) + return _process_c_function(other, self, backend.pow) # Reflected Array Operators @@ -563,31 +564,31 @@ def __rand__(self, other: Array, /) -> Array: """ Return other & self. """ - return _process_c_function(other, self, library.bitand) + return _process_c_function(other, self, backend.bitand) def __ror__(self, other: Array, /) -> Array: """ Return other | self. """ - return _process_c_function(other, self, library.bitor) + return _process_c_function(other, self, backend.bitor) def __rxor__(self, other: Array, /) -> Array: """ Return other ^ self. """ - return _process_c_function(other, self, library.bitxor) + return _process_c_function(other, self, backend.bitxor) def __rlshift__(self, other: Array, /) -> Array: """ Return other << self. """ - return _process_c_function(other, self, library.bitshiftl) + return _process_c_function(other, self, backend.bitshiftl) def __rrshift__(self, other: Array, /) -> Array: """ Return other >> self. """ - return _process_c_function(other, self, library.bitshiftr) + return _process_c_function(other, self, backend.bitshiftr) # In-place Arithmetic Operators @@ -596,25 +597,25 @@ def __iadd__(self, other: Union[int, float, Array], /) -> Array: """ Return self += other. """ - return _process_c_function(self, other, library.add) + return _process_c_function(self, other, backend.add) def __isub__(self, other: Union[int, float, Array], /) -> Array: """ Return self -= other. """ - return _process_c_function(self, other, library.sub) + return _process_c_function(self, other, backend.sub) def __imul__(self, other: Union[int, float, Array], /) -> Array: """ Return self *= other. """ - return _process_c_function(self, other, library.mul) + return _process_c_function(self, other, backend.mul) def __itruediv__(self, other: Union[int, float, Array], /) -> Array: """ Return self /= other. """ - return _process_c_function(self, other, library.div) + return _process_c_function(self, other, backend.div) def __ifloordiv__(self, other: Union[int, float, Array], /) -> Array: # TODO @@ -624,13 +625,13 @@ def __imod__(self, other: Union[int, float, Array], /) -> Array: """ Return self %= other. """ - return _process_c_function(self, other, library.mod) + return _process_c_function(self, other, backend.mod) def __ipow__(self, other: Union[int, float, Array], /) -> Array: """ Return self **= other. """ - return _process_c_function(self, other, library.pow) + return _process_c_function(self, other, backend.pow) # In-place Array Operators @@ -644,31 +645,31 @@ def __iand__(self, other: Union[int, bool, Array], /) -> Array: """ Return self &= other. """ - return _process_c_function(self, other, library.bitand) + return _process_c_function(self, other, backend.bitand) def __ior__(self, other: Union[int, bool, Array], /) -> Array: """ Return self |= other. """ - return _process_c_function(self, other, library.bitor) + return _process_c_function(self, other, backend.bitor) def __ixor__(self, other: Union[int, bool, Array], /) -> Array: """ Return self ^= other. """ - return _process_c_function(self, other, library.bitxor) + return _process_c_function(self, other, backend.bitxor) def __ilshift__(self, other: Union[int, Array], /) -> Array: """ Return self <<= other. """ - return _process_c_function(self, other, library.bitshiftl) + return _process_c_function(self, other, backend.bitshiftl) def __irshift__(self, other: Union[int, Array], /) -> Array: """ Return self >>= other. """ - return _process_c_function(self, other, library.bitshiftr) + return _process_c_function(self, other, backend.bitshiftr) # Methods diff --git a/arrayfire/array_api/backend/__init__.py b/arrayfire/array_api/backend/__init__.py index 053c20d5e..30253b4fb 100644 --- a/arrayfire/array_api/backend/__init__.py +++ b/arrayfire/array_api/backend/__init__.py @@ -1,37 +1,10 @@ -import ctypes -from dataclasses import dataclass -from typing import Any, Callable - -from ..dtypes.helpers import c_dim_t, to_str -from .constants import ErrorCodes - -backend_api = ctypes.CDLL("/opt/arrayfire//lib/libafcpu.3.dylib") # Mock - - -class safe_call: - def __init__(self, c_function: Callable) -> None: - self.c_function = c_function - - def __call__(self, *args: Any) -> None: - c_err = self.c_function(*args) - if c_err == ErrorCodes.none.value: - return - - err_str = ctypes.c_char_p(0) - backend_api.af_get_last_error(ctypes.pointer(err_str), ctypes.pointer(c_dim_t(0))) - raise RuntimeError(to_str(err_str)) - - -def safe_call_func(c_err): - if c_err == ErrorCodes.none.value: - return - - err_str = ctypes.c_char_p(0) - backend_api.af_get_last_error(ctypes.pointer(err_str), ctypes.pointer(c_dim_t(0))) - raise RuntimeError(to_str(err_str)) - - -@dataclass -class ArrayBuffer: - address: int - length: int = 0 +__all__ = [ + # Backend + "ArrayBuffer", + # Operators + "add", "sub", "mul", "div", "mod", "pow", "bitnot", "bitand", "bitor", "bitxor", "bitshiftl", "bitshiftr", "lt", + "le", "gt", "ge", "eq", "neq"] + +from .backend import ArrayBuffer +from .operators import ( + add, bitand, bitnot, bitor, bitshiftl, bitshiftr, bitxor, div, eq, ge, gt, le, lt, mod, mul, neq, pow, sub) diff --git a/arrayfire/array_api/backend/backend.py b/arrayfire/array_api/backend/backend.py new file mode 100644 index 000000000..a154555f5 --- /dev/null +++ b/arrayfire/array_api/backend/backend.py @@ -0,0 +1,26 @@ +import ctypes +import enum +from dataclasses import dataclass + +from ..dtypes.helpers import c_dim_t, to_str + +backend_api = ctypes.CDLL("/opt/arrayfire//lib/libafcpu.3.dylib") # Mock + + +def safe_call(c_err: int) -> None: + if c_err == _ErrorCodes.none.value: + return + + err_str = ctypes.c_char_p(0) + backend_api.af_get_last_error(ctypes.pointer(err_str), ctypes.pointer(c_dim_t(0))) + raise RuntimeError(to_str(err_str)) + + +class _ErrorCodes(enum.Enum): + none = 0 + + +@dataclass +class ArrayBuffer: + address: int + length: int = 0 diff --git a/arrayfire/array_api/backend/constant_array.py b/arrayfire/array_api/backend/constant_array.py index daf58b78c..85ba3d8bf 100644 --- a/arrayfire/array_api/backend/constant_array.py +++ b/arrayfire/array_api/backend/constant_array.py @@ -3,7 +3,7 @@ from ..dtypes import Dtype, int64, uint64 from ..dtypes.helpers import CShape, implicit_dtype -from . import backend_api, safe_call_func +from .backend import backend_api, safe_call AFArray = ctypes.c_void_p @@ -15,7 +15,7 @@ def _constant_complex(number: Union[int, float], shape: Tuple[int, ...], dtype: out = ctypes.c_void_p(0) c_shape = CShape(*shape) - safe_call_func( + safe_call( backend_api.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) @@ -30,7 +30,7 @@ def _constant_long(number: Union[int, float], shape: Tuple[int, ...], dtype: Dty out = ctypes.c_void_p(0) c_shape = CShape(*shape) - safe_call_func( + safe_call( backend_api.af_constant_long( ctypes.pointer(out), ctypes.c_longlong(number.real), 4, ctypes.pointer(c_shape.c_array)) ) @@ -45,7 +45,7 @@ def _constant_ulong(number: Union[int, float], shape: Tuple[int, ...], dtype: Dt out = ctypes.c_void_p(0) c_shape = CShape(*shape) - safe_call_func( + safe_call( backend_api.af_constant_ulong( ctypes.pointer(out), ctypes.c_ulonglong(number.real), 4, ctypes.pointer(c_shape.c_array)) ) @@ -59,7 +59,7 @@ def _constant(number: Union[int, float], shape: Tuple[int, ...], dtype: Dtype, / out = ctypes.c_void_p(0) c_shape = CShape(*shape) - safe_call_func( + safe_call( backend_api.af_constant( ctypes.pointer(out), ctypes.c_double(number), 4, ctypes.pointer(c_shape.c_array), dtype.c_api_value) ) diff --git a/arrayfire/array_api/backend/constants.py b/arrayfire/array_api/backend/constants.py deleted file mode 100644 index 58b889928..000000000 --- a/arrayfire/array_api/backend/constants.py +++ /dev/null @@ -1,38 +0,0 @@ -import enum - - -class ErrorCodes(enum.Enum): - none = 0 - - # 100-199 Errors in environment - no_mem = 101 - driver = 102 - runtime = 103 - - # 200-299 Errors in input parameters - invalid_array = 201 - arg = 202 - size = 203 - type = 204 - diff_type = 205 - batch = 207 - device = 208 - - # 300-399 Errors for missing software features - not_supported = 301 - not_configured = 302 - nonfree = 303 - - # 400-499 Errors for missing hardware features - no_dbl = 401 - no_gfx = 402 - no_half = 403 - - # 500-599 Errors specific to the heterogeneous API - load_lib = 501 - load_sym = 502 - arr_bknd_mismatch = 503 - - # 900-999 Errors from upstream libraries and runtimes - internal = 998 - unknown = 999 diff --git a/arrayfire/array_api/backend/library.py b/arrayfire/array_api/backend/library.py index 4a1e27d75..3fb956056 100644 --- a/arrayfire/array_api/backend/library.py +++ b/arrayfire/array_api/backend/library.py @@ -4,7 +4,7 @@ from ..device import PointerSource from ..dtypes import CType, Dtype from ..dtypes.helpers import CShape, c_dim_t -from . import ArrayBuffer, backend_api, safe_call, safe_call_func +from .backend import ArrayBuffer, backend_api, safe_call AFArrayPointer = ctypes._Pointer AFArray = ctypes.c_void_p @@ -22,7 +22,7 @@ def create_handle(shape: Tuple[int, ...], dtype: Dtype, /) -> AFArray: out = ctypes.c_void_p(0) c_shape = CShape(*shape) - safe_call_func( + safe_call( backend_api.af_create_handle( ctypes.pointer(out), c_shape.original_shape, ctypes.pointer(c_shape.c_array), dtype.c_api_value) ) @@ -35,7 +35,7 @@ def retain_array(arr: AFArray) -> AFArray: """ out = ctypes.c_void_p(0) - safe_call_func( + safe_call( backend_api.af_retain_array(ctypes.pointer(out), arr) ) return out @@ -48,7 +48,7 @@ def create_array(shape: Tuple[int, ...], dtype: Dtype, array_buffer: ArrayBuffer out = ctypes.c_void_p(0) c_shape = CShape(*shape) - safe_call_func( + safe_call( backend_api.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) @@ -63,7 +63,7 @@ def device_array(shape: Tuple[int, ...], dtype: Dtype, array_buffer: ArrayBuffer out = ctypes.c_void_p(0) c_shape = CShape(*shape) - safe_call_func( + safe_call( backend_api.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) @@ -89,7 +89,7 @@ def create_strided_array( if len(strides) < 4: strides += (strides[-1], ) * (4 - len(strides)) - safe_call_func( + safe_call( backend_api.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) @@ -103,7 +103,7 @@ def get_ctype(arr: AFArray) -> int: """ out = ctypes.c_int() - safe_call_func( + safe_call( backend_api.af_get_type(ctypes.pointer(out), arr) ) return out.value @@ -115,7 +115,7 @@ def get_elements(arr: AFArray) -> int: """ out = c_dim_t(0) - safe_call_func( + safe_call( backend_api.af_get_elements(ctypes.pointer(out), arr) ) return out.value @@ -127,13 +127,12 @@ def get_numdims(arr: AFArray) -> int: """ out = ctypes.c_uint(0) - safe_call_func( + safe_call( backend_api.af_get_numdims(ctypes.pointer(out), arr) ) return out.value -@safe_call def af_get_dims(d0, d1, d2, d3, arr: AFArray, /) -> Any: # FIXME """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga8b90da50a532837d9763e301b2267348 @@ -141,7 +140,6 @@ def af_get_dims(d0, d1, d2, d3, arr: AFArray, /) -> Any: # FIXME return backend_api.af_get_dims(d0, d1, d2, d3, arr) -@safe_call def af_get_scalar(output_value, arr: AFArray, /) -> Any: # FIXME """ source: https://arrayfire.org/docs/group__c__api__mat.htm#gaefe2e343a74a84bd43b588218ecc09a3 @@ -149,7 +147,6 @@ def af_get_scalar(output_value, arr: AFArray, /) -> Any: # FIXME return backend_api.af_get_scalar(output_value, arr) -@safe_call def af_is_empty(result, arr: AFArray, /) -> Any: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga19c749e95314e1c77d816ad9952fb680 @@ -157,7 +154,6 @@ def af_is_empty(result, arr: AFArray, /) -> Any: return backend_api.af_is_empty(result, arr) -@safe_call def af_get_data_ptr(data, arr: AFArray, /) -> Any: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga6040dc6f0eb127402fbf62c1165f0b9d @@ -168,7 +164,6 @@ def af_get_data_ptr(data, arr: AFArray, /) -> Any: # Arrayfire Functions -@safe_call def af_index_gen(out: AFArrayPointer, in_: AFArray, ndims: int, indices, /) -> Any: # FIXME indices """ source: https://arrayfire.org/docs/group__index__func__index.htm#ga14a7d149dba0ed0b977335a3df9d91e6 @@ -176,7 +171,6 @@ def af_index_gen(out: AFArrayPointer, in_: AFArray, ndims: int, indices, /) -> A return backend_api.af_index_gen(out, in_, ndims, indices) -@safe_call def af_transpose(out: AFArrayPointer, in_: AFArray, conjugate: bool, /) -> Any: """ https://arrayfire.org/docs/group__blas__func__transpose.htm#ga716b2b9bf190c8f8d0970aef2b57d8e7 @@ -184,7 +178,6 @@ def af_transpose(out: AFArrayPointer, in_: AFArray, conjugate: bool, /) -> Any: return backend_api.af_transpose(out, in_, conjugate) -@safe_call def af_reorder(out: AFArrayPointer, in_: AFArray, x, y, z, w, /) -> Any: # FIXME """ source: https://arrayfire.org/docs/group__manip__func__reorder.htm#ga57383f4d00a3a86eab08dddd52c3ad3d @@ -192,7 +185,6 @@ def af_reorder(out: AFArrayPointer, in_: AFArray, x, y, z, w, /) -> Any: # FIXM return backend_api.af_reorder(out, in_, x, y, z, w) -@safe_call def af_array_to_string(output, exp, arr: AFArray, precision: int, transpose: bool, /) -> Any: # FIXME """ source: https://arrayfire.org/docs/group__print__func__tostring.htm#ga01f32ef2420b5d4592c6e4b4964b863b @@ -200,228 +192,8 @@ def af_array_to_string(output, exp, arr: AFArray, precision: int, transpose: boo return backend_api.af_array_to_string(output, exp, arr, precision, transpose) -@safe_call def af_free_host(ptr) -> Any: # FIXME """ source: https://arrayfire.org/docs/group__device__func__free__host.htm#ga3f1149a837a7ebbe8002d5d2244e3370 """ return backend_api.af_free_host(ptr) - -# Arithmetic Operators - - -def add(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__add.htm#ga1dfbee755fedd680f4476803ddfe06a7 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_add(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def sub(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__sub.htm#ga80ff99a2e186c23614ea9f36ffc6f0a4 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_sub(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def mul(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__mul.htm#ga5f7588b2809ff7551d38b6a0bd583a02 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_mul(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def div(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__div.htm#ga21f3f97755702692ec8976934e75fde6 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_div(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def mod(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__mod.htm#ga01924d1b59d8886e46fabd2dc9b27e0f - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_mod(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def pow(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__pow.htm#ga0f28be1a9c8b176a78c4a47f483e7fc6 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_pow(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -# Bitwise Operators - -def bitnot(arr: AFArray, /) -> Any: - """ - source: https://arrayfire.org/docs/group__arith__func__bitnot.htm#gaf97e8a38aab59ed2d3a742515467d01e - """ - out = ctypes.c_void_p(0) - safe_call_func( - backend_api.af_pow(ctypes.pointer(out), arr) - ) - return out - - -def bitand(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__bitand.htm#ga45c0779ade4703708596df11cca98800 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_bitand(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def bitor(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__bitor.htm#ga84c99f77d1d83fd53f949b4d67b5b210 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_bitor(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def bitxor(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__bitxor.htm#ga8188620da6b432998e55fdd1fad22100 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_bitxor(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def bitshiftl(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__shiftl.htm#ga3139645aafe6f045a5cab454e9c13137 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_bitshiftl(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def bitshiftr(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__shiftr.htm#ga4c06b9977ecf96cdfc83b5dfd1ac4895 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_bitshiftr(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def lt(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/arith_8h.htm#ae7aa04bf23b32bb11c4bab8bdd637103 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_lt(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def le(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__le.htm#gad5535ce64dbed46d0773fd494e84e922 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_le(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def gt(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__gt.htm#ga4e65603259515de8939899a163ebaf9e - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_gt(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def ge(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__ge.htm#ga4513f212e0b0a22dcf4653e89c85e3d9 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_ge(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def eq(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__eq.htm#ga76d2da7716831616bb81effa9e163693 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_eq(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out - - -def neq(lhs: AFArray, rhs: AFArray, /) -> AFArray: - """ - source: https://arrayfire.org/docs/group__arith__func__neq.htm#gae4ee8bd06a410f259f1493fb811ce441 - """ - out = ctypes.c_void_p(0) - - safe_call_func( - backend_api.af_neq(ctypes.pointer(out), lhs, rhs, _bcast_var) - ) - return out diff --git a/arrayfire/array_api/backend/operators.py b/arrayfire/array_api/backend/operators.py new file mode 100644 index 000000000..96460f3ac --- /dev/null +++ b/arrayfire/array_api/backend/operators.py @@ -0,0 +1,149 @@ +import ctypes +from typing import Callable + +from .backend import backend_api, safe_call + +AFArray = ctypes.c_void_p + +# HACK, TODO replace for actual bcast_var after refactoring ~ https://github.com/arrayfire/arrayfire/pull/2871 +_bcast_var = False + +# Arithmetic Operators + + +def add(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__add.htm#ga1dfbee755fedd680f4476803ddfe06a7 + """ + return _binary_op(backend_api.af_add, lhs, rhs) + + +def sub(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__sub.htm#ga80ff99a2e186c23614ea9f36ffc6f0a4 + """ + return _binary_op(backend_api.af_sub, lhs, rhs) + + +def mul(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__mul.htm#ga5f7588b2809ff7551d38b6a0bd583a02 + """ + return _binary_op(backend_api.af_mul, lhs, rhs) + + +def div(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__div.htm#ga21f3f97755702692ec8976934e75fde6 + """ + return _binary_op(backend_api.af_div, lhs, rhs) + + +def mod(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__mod.htm#ga01924d1b59d8886e46fabd2dc9b27e0f + """ + return _binary_op(backend_api.af_mod, lhs, rhs) + + +def pow(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__pow.htm#ga0f28be1a9c8b176a78c4a47f483e7fc6 + """ + return _binary_op(backend_api.af_pow, lhs, rhs) + + +# Bitwise Operators + +def bitnot(arr: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__bitnot.htm#gaf97e8a38aab59ed2d3a742515467d01e + """ + out = ctypes.c_void_p(0) + safe_call( + backend_api.af_bitnot(ctypes.pointer(out), arr) + ) + return out + + +def bitand(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__bitand.htm#ga45c0779ade4703708596df11cca98800 + """ + return _binary_op(backend_api.af_bitand, lhs, rhs) + + +def bitor(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__bitor.htm#ga84c99f77d1d83fd53f949b4d67b5b210 + """ + return _binary_op(backend_api.af_bitor, lhs, rhs) + + +def bitxor(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__bitxor.htm#ga8188620da6b432998e55fdd1fad22100 + """ + return _binary_op(backend_api.af_bitxor, lhs, rhs) + + +def bitshiftl(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__shiftl.htm#ga3139645aafe6f045a5cab454e9c13137 + """ + return _binary_op(backend_api.af_butshiftl, lhs, rhs) + + +def bitshiftr(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__shiftr.htm#ga4c06b9977ecf96cdfc83b5dfd1ac4895 + """ + return _binary_op(backend_api.af_bitshiftr, lhs, rhs) + + +def lt(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/arith_8h.htm#ae7aa04bf23b32bb11c4bab8bdd637103 + """ + return _binary_op(backend_api.af_lt, lhs, rhs) + + +def le(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__le.htm#gad5535ce64dbed46d0773fd494e84e922 + """ + return _binary_op(backend_api.af_le, lhs, rhs) + + +def gt(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__gt.htm#ga4e65603259515de8939899a163ebaf9e + """ + return _binary_op(backend_api.af_gt, lhs, rhs) + + +def ge(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__ge.htm#ga4513f212e0b0a22dcf4653e89c85e3d9 + """ + return _binary_op(backend_api.af_ge, lhs, rhs) + + +def eq(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__eq.htm#ga76d2da7716831616bb81effa9e163693 + """ + return _binary_op(backend_api.af_eq, lhs, rhs) + + +def neq(lhs: AFArray, rhs: AFArray, /) -> AFArray: + """ + source: https://arrayfire.org/docs/group__arith__func__neq.htm#gae4ee8bd06a410f259f1493fb811ce441 + """ + return _binary_op(backend_api.af_neq, lhs, rhs) + + +def _binary_op(c_func: Callable, lhs: AFArray, rhs: AFArray, /) -> AFArray: + out = ctypes.c_void_p(0) + safe_call(c_func(ctypes.pointer(out), lhs, rhs, _bcast_var)) + return out diff --git a/arrayfire/array_api/operators.py b/arrayfire/array_api/operators.py index 8cc6f8b56..7cb4c075b 100644 --- a/arrayfire/array_api/operators.py +++ b/arrayfire/array_api/operators.py @@ -1,7 +1,7 @@ from typing import Callable +from . import backend from .array_object import Array -from .backend import library class return_copy: @@ -17,9 +17,9 @@ def __call__(self, x1: Array, x2: Array) -> Array: @return_copy def add(x1: Array, x2: Array, /) -> Array: - return library.add(x1, x2) + return backend.add(x1, x2) @return_copy def sub(x1: Array, x2: Array, /) -> Array: - return library.sub(x1, x2) + return backend.sub(x1, x2) From 0b164d4876d70bc1eed89c609fb4d26e3e635f7a Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 11 Feb 2023 03:45:57 +0200 Subject: [PATCH 32/36] Refactor used in array_object backend methods --- arrayfire/array_api/array_object.py | 80 ++++------------ arrayfire/array_api/backend/library.py | 91 +++++++++++++------ arrayfire/array_api/dtypes/__init__.py | 4 + arrayfire/array_api/dtypes/helpers.py | 18 +++- .../array_api/tests/test_array_object.py | 1 + 5 files changed, 106 insertions(+), 88 deletions(-) diff --git a/arrayfire/array_api/array_object.py b/arrayfire/array_api/array_object.py index de9e7092a..cdae8da94 100644 --- a/arrayfire/array_api/array_object.py +++ b/arrayfire/array_api/array_object.py @@ -7,7 +7,7 @@ # TODO replace imports from original lib with refactored ones from arrayfire.algorithm import count -from arrayfire.array import _get_indices, _in_display_dims_limit +from arrayfire.array import _in_display_dims_limit from . import backend from .backend import ArrayBuffer, library @@ -16,8 +16,7 @@ from .dtypes import CType from .dtypes import bool as af_bool from .dtypes import float32 as af_float32 -from .dtypes import supported_dtypes -from .dtypes.helpers import CShape, Dtype, c_dim_t, to_str +from .dtypes.helpers import Dtype, c_api_value_to_dtype, str_to_dtype # TODO use int | float in operators -> remove bool | complex support @@ -31,7 +30,7 @@ def __init__( _no_initial_dtype = False # HACK, FIXME if isinstance(dtype, str): - dtype = _str_to_dtype(dtype) # type: ignore[arg-type] + dtype = str_to_dtype(dtype) # type: ignore[arg-type] if dtype is None: _no_initial_dtype = True @@ -59,7 +58,7 @@ def __init__( _array_buffer = ArrayBuffer(*_array.buffer_info()) elif isinstance(x, int) or isinstance(x, ctypes.c_void_p): # TODO - _array_buffer = ArrayBuffer(x if not isinstance(x, ctypes.c_void_p) else x.value) + _array_buffer = ArrayBuffer(x if not isinstance(x, ctypes.c_void_p) else x.value) # type: ignore[arg-type] if not shape: raise TypeError("Expected to receive the initial shape due to the x being a data pointer.") @@ -89,7 +88,8 @@ def __init__( self.arr = library.device_array(shape, dtype, _array_buffer) return - self.arr = library.create_strided_array(shape, dtype, _array_buffer, offset, strides, pointer_source) + self.arr = library.create_strided_array( + shape, dtype, _array_buffer, offset, strides, pointer_source) # type: ignore[arg-type] # Arithmetic Operators @@ -728,7 +728,8 @@ def __getitem__(self, key: Union[int, slice, Tuple[Union[int, slice, ], ...], Ar if count(key) == 0: return out - library.af_index_gen(ctypes.pointer(out.arr), self.arr, c_dim_t(ndims), _get_indices(key).pointer) + # HACK known issue + out.arr = library.index_gen(self.arr, ndims, key) # type: ignore[arg-type] return out def __index__(self) -> int: @@ -753,12 +754,12 @@ def __str__(self) -> str: if not _in_display_dims_limit(self.shape): return _metadata_string(self.dtype, self.shape) - return _metadata_string(self.dtype) + _array_as_str(self) + return _metadata_string(self.dtype) + library.array_as_str(self.arr) def __repr__(self) -> str: # return _metadata_string(self.dtype, self.shape) # TODO change the look of array representation. E.g., like np.array - return _array_as_str(self) + return library.array_as_str(self.arr) def to_device(self, device: Any, /, *, stream: Union[int, Any] = None) -> Array: # TODO implementation and change device type from Any to Device @@ -776,8 +777,7 @@ def dtype(self) -> Dtype: out : Dtype Array data type. """ - ctype = library.get_ctype(self.arr) - return _c_api_value_to_dtype(ctype) + return c_api_value_to_dtype(library.get_ctype(self.arr)) @property def device(self) -> Any: @@ -810,7 +810,7 @@ def T(self) -> Array: # TODO add check if out.dtype == self.dtype out = Array() - library.af_transpose(ctypes.pointer(out.arr), self.arr, False) + out.arr = library.transpose(self.arr, False) return out @property @@ -850,13 +850,8 @@ def shape(self) -> Tuple[int, ...]: out : tuple[int, ...] Array dimensions. """ - # TODO refactor - d0 = c_dim_t(0) - d1 = c_dim_t(0) - d2 = c_dim_t(0) - d3 = c_dim_t(0) - library.af_get_dims(ctypes.pointer(d0), ctypes.pointer(d1), ctypes.pointer(d2), ctypes.pointer(d3), self.arr) - return (d0.value, d1.value, d2.value, d3.value)[:self.ndim] # Skip passing None values + # NOTE skipping passing any None values + return library.get_dims(self.arr)[:self.ndim] def scalar(self) -> Union[None, int, float, bool, complex]: """ @@ -866,24 +861,20 @@ def scalar(self) -> Union[None, int, float, bool, complex]: if self.is_empty(): return None - out = self.dtype.c_type() - library.af_get_scalar(ctypes.pointer(out), self.arr) - return out.value # type: ignore[no-any-return] # FIXME + return library.get_scalar(self.arr, self.dtype) def is_empty(self) -> bool: """ Check if the array is empty i.e. it has no elements. """ - out = ctypes.c_bool() - library.af_is_empty(ctypes.pointer(out), self.arr) - return out.value + return library.is_empty(self.arr) def to_list(self, row_major: bool = False) -> List[Union[None, int, float, bool, complex]]: if self.is_empty(): return [] array = _reorder(self) if row_major else self - ctypes_array = _get_ctypes_array(array) + ctypes_array = library.get_data_ptr(array.arr, array.size, array.dtype) if array.ndim == 1: return list(ctypes_array) @@ -904,14 +895,7 @@ def to_ctype_array(self, row_major: bool = False) -> ctypes.Array: raise RuntimeError("Can not convert an empty array to ctype.") array = _reorder(self) if row_major else self - return _get_ctypes_array(array) - - -def _get_ctypes_array(array: Array) -> ctypes.Array: - c_shape = array.dtype.c_type * array.size - ctypes_array = c_shape() - library.af_get_data_ptr(ctypes.pointer(ctypes_array), array.arr) - return ctypes_array + return library.get_data_ptr(array.arr, array.size, array.dtype) def _reorder(array: Array) -> Array: @@ -922,20 +906,10 @@ def _reorder(array: Array) -> Array: return array out = Array() - c_shape = CShape(*(tuple(reversed(range(array.ndim))) + tuple(range(array.ndim, 4)))) - library.af_reorder(ctypes.pointer(out.arr), array.arr, *c_shape) + out.arr = library.reorder(array.arr, array.ndim) return out -def _array_as_str(array: Array) -> str: - arr_str = ctypes.c_char_p(0) - # FIXME add description to passed arguments - library.af_array_to_string(ctypes.pointer(arr_str), "", array.arr, 4, True) - py_str = to_str(arr_str) - library.af_free_host(arr_str) - return py_str - - def _metadata_string(dtype: Dtype, dims: Optional[Tuple[int, ...]] = None) -> str: return ( "arrayfire.Array()\n" @@ -943,22 +917,6 @@ def _metadata_string(dtype: Dtype, dims: Optional[Tuple[int, ...]] = None) -> st f"Dims: {str(dims) if dims else ''}") -def _c_api_value_to_dtype(value: int) -> Dtype: - for dtype in supported_dtypes: - if value == dtype.c_api_value: - return dtype - - raise TypeError("There is no supported dtype that matches passed dtype C API value.") - - -def _str_to_dtype(value: int) -> Dtype: - for dtype in supported_dtypes: - if value == dtype.typecode or value == dtype.typename: - return dtype - - raise TypeError("There is no supported dtype that matches passed dtype typecode.") - - def _process_c_function(lhs: Union[int, float, Array], rhs: Union[int, float, Array], c_function: Any) -> Array: out = Array() diff --git a/arrayfire/array_api/backend/library.py b/arrayfire/array_api/backend/library.py index 3fb956056..9c87aa5be 100644 --- a/arrayfire/array_api/backend/library.py +++ b/arrayfire/array_api/backend/library.py @@ -1,9 +1,11 @@ import ctypes -from typing import Any, Tuple +from typing import Tuple, Union, cast + +from arrayfire.array import _get_indices # HACK replace with refactored one from ..device import PointerSource from ..dtypes import CType, Dtype -from ..dtypes.helpers import CShape, c_dim_t +from ..dtypes.helpers import CShape, c_dim_t, to_str from .backend import ArrayBuffer, backend_api, safe_call AFArrayPointer = ctypes._Pointer @@ -133,67 +135,104 @@ def get_numdims(arr: AFArray) -> int: return out.value -def af_get_dims(d0, d1, d2, d3, arr: AFArray, /) -> Any: # FIXME +def get_dims(arr: AFArray) -> Tuple[int, ...]: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga8b90da50a532837d9763e301b2267348 """ - return backend_api.af_get_dims(d0, d1, d2, d3, arr) + d0 = c_dim_t(0) + d1 = c_dim_t(0) + d2 = c_dim_t(0) + d3 = c_dim_t(0) + + safe_call( + backend_api.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 af_get_scalar(output_value, arr: AFArray, /) -> Any: # FIXME +def get_scalar(arr: AFArray, dtype: Dtype, /) -> Union[None, int, float, bool, complex]: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#gaefe2e343a74a84bd43b588218ecc09a3 """ - return backend_api.af_get_scalar(output_value, arr) + out = dtype.c_type() + safe_call( + backend_api.af_get_scalar(ctypes.pointer(out), arr) + ) + return cast(Union[None, int, float, bool, complex], out.value) -def af_is_empty(result, arr: AFArray, /) -> Any: +def is_empty(arr: AFArray) -> bool: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga19c749e95314e1c77d816ad9952fb680 """ - return backend_api.af_is_empty(result, arr) + out = ctypes.c_bool() + safe_call( + backend_api.af_is_empty(ctypes.pointer(out), arr) + ) + return out.value -def af_get_data_ptr(data, arr: AFArray, /) -> Any: +def get_data_ptr(arr: AFArray, size: int, dtype: Dtype, /) -> ctypes.Array: """ source: https://arrayfire.org/docs/group__c__api__mat.htm#ga6040dc6f0eb127402fbf62c1165f0b9d """ - return backend_api.af_get_data_ptr(data, arr) + c_shape = dtype.c_type * size + ctypes_array = c_shape() + safe_call( + backend_api.af_get_data_ptr(ctypes.pointer(ctypes_array), arr) + ) + return ctypes_array # Arrayfire Functions -def af_index_gen(out: AFArrayPointer, in_: AFArray, ndims: int, indices, /) -> Any: # FIXME indices +def index_gen(arr: AFArray, ndims: int, key: Union[int, slice, Tuple[Union[int, slice, ], ...]], /) -> AFArray: """ source: https://arrayfire.org/docs/group__index__func__index.htm#ga14a7d149dba0ed0b977335a3df9d91e6 """ - return backend_api.af_index_gen(out, in_, ndims, indices) + out = ctypes.c_void_p(0) + safe_call( + backend_api.af_index_gen(ctypes.pointer(out), arr, c_dim_t(ndims), _get_indices(key).pointer) + ) + return out -def af_transpose(out: AFArrayPointer, in_: AFArray, conjugate: bool, /) -> Any: +def transpose(arr: AFArray, conjugate: bool, /) -> AFArray: """ https://arrayfire.org/docs/group__blas__func__transpose.htm#ga716b2b9bf190c8f8d0970aef2b57d8e7 """ - return backend_api.af_transpose(out, in_, conjugate) + out = ctypes.c_void_p(0) + safe_call( + backend_api.af_transpose(ctypes.pointer(out), arr, conjugate) + ) + return out -def af_reorder(out: AFArrayPointer, in_: AFArray, x, y, z, w, /) -> Any: # FIXME +def reorder(arr: AFArray, ndims: int, /) -> AFArray: """ source: https://arrayfire.org/docs/group__manip__func__reorder.htm#ga57383f4d00a3a86eab08dddd52c3ad3d """ - return backend_api.af_reorder(out, in_, x, y, z, w) - - -def af_array_to_string(output, exp, arr: AFArray, precision: int, transpose: bool, /) -> Any: # FIXME - """ - source: https://arrayfire.org/docs/group__print__func__tostring.htm#ga01f32ef2420b5d4592c6e4b4964b863b - """ - return backend_api.af_array_to_string(output, exp, arr, precision, transpose) + out = ctypes.c_void_p(0) + c_shape = CShape(*(tuple(reversed(range(ndims))) + tuple(range(ndims, 4)))) + safe_call( + backend_api.af_reorder(ctypes.pointer(out), arr, *c_shape) + ) + return out -def af_free_host(ptr) -> Any: # FIXME +def array_as_str(arr: AFArray) -> str: """ - source: https://arrayfire.org/docs/group__device__func__free__host.htm#ga3f1149a837a7ebbe8002d5d2244e3370 + source: + - https://arrayfire.org/docs/group__print__func__tostring.htm#ga01f32ef2420b5d4592c6e4b4964b863b + - https://arrayfire.org/docs/group__device__func__free__host.htm#ga3f1149a837a7ebbe8002d5d2244e3370 """ - return backend_api.af_free_host(ptr) + arr_str = ctypes.c_char_p(0) + safe_call( + backend_api.af_array_to_string(ctypes.pointer(arr_str), "", arr, 4, True) + ) + py_str = to_str(arr_str) + safe_call( + backend_api.af_free_host(arr_str) + ) + return py_str diff --git a/arrayfire/array_api/dtypes/__init__.py b/arrayfire/array_api/dtypes/__init__.py index d0d015603..e9a181aa3 100644 --- a/arrayfire/array_api/dtypes/__init__.py +++ b/arrayfire/array_api/dtypes/__init__.py @@ -1,5 +1,9 @@ from __future__ import annotations +__all__ = [ + "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float32", "float64", "complex64", + "complex128", "bool"] + import ctypes from dataclasses import dataclass from typing import Type diff --git a/arrayfire/array_api/dtypes/helpers.py b/arrayfire/array_api/dtypes/helpers.py index 7586c3bce..cf4d3064b 100644 --- a/arrayfire/array_api/dtypes/helpers.py +++ b/arrayfire/array_api/dtypes/helpers.py @@ -6,7 +6,7 @@ from ..config import is_arch_x86 from . import Dtype from . import bool as af_bool -from . import complex64, complex128, float32, float64, int64 +from . import complex64, complex128, float32, float64, int64, supported_dtypes c_dim_t = ctypes.c_int if is_arch_x86() else ctypes.c_longlong ShapeType = Tuple[int, ...] @@ -58,3 +58,19 @@ def implicit_dtype(number: Union[int, float], array_dtype: Dtype) -> Dtype: return complex64 return number_dtype + + +def c_api_value_to_dtype(value: int) -> Dtype: + for dtype in supported_dtypes: + if value == dtype.c_api_value: + return dtype + + raise TypeError("There is no supported dtype that matches passed dtype C API value.") + + +def str_to_dtype(value: int) -> Dtype: + for dtype in supported_dtypes: + if value == dtype.typecode or value == dtype.typename: + return dtype + + raise TypeError("There is no supported dtype that matches passed dtype typecode.") diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index 0204542cc..019c5f986 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -143,6 +143,7 @@ def test_array_from_unsupported_type() -> None: def test_array_getitem() -> None: array = Array([1, 2, 3, 4, 5]) + # import ipdb; ipdb.set_trace() int_item = array[2] assert array.dtype == int_item.dtype assert int_item.scalar() == 3 From 282f86060e31f2655b0c1aac739bb9b36b736717 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 11 Feb 2023 03:50:10 +0200 Subject: [PATCH 33/36] Minor test fix --- arrayfire/array_api/tests/test_array_object.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py index 019c5f986..5f66a9cab 100644 --- a/arrayfire/array_api/tests/test_array_object.py +++ b/arrayfire/array_api/tests/test_array_object.py @@ -80,7 +80,7 @@ def test_create_array_from_1d_list() -> None: def test_create_array_from_2d_list() -> None: with pytest.raises(TypeError): - Array([[1, 2, 3], [1, 2, 3]]) + Array([[1, 2, 3], [1, 2, 3]]) # type: ignore[list-item] def test_create_array_from_pyarray() -> None: @@ -143,7 +143,6 @@ def test_array_from_unsupported_type() -> None: def test_array_getitem() -> None: array = Array([1, 2, 3, 4, 5]) - # import ipdb; ipdb.set_trace() int_item = array[2] assert array.dtype == int_item.dtype assert int_item.scalar() == 3 @@ -176,7 +175,7 @@ def setup_method(self, method: Any) -> None: self.list = [1, 2, 3] self.const_int = 2 self.const_float = 1.5 - self.array = Array(self.list) + self.array = Array(self.list) # FIXME typing self.array_other = Array([9, 9, 9]) self.tuple = (1, 2, 3) From 54f7adadd776487d3f789f02d81ea054ab790d82 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 13 Feb 2023 21:54:04 +0200 Subject: [PATCH 34/36] Refactor tests --- .../array_api/tests/array_object/__init__.py | 0 .../tests/array_object/test_initialization.py | 53 ++ .../tests/array_object/test_methods.py | 31 ++ .../tests/array_object/test_operators.py | 80 +++ .../array_api/tests/test_array_object.py | 459 ------------------ 5 files changed, 164 insertions(+), 459 deletions(-) create mode 100644 arrayfire/array_api/tests/array_object/__init__.py create mode 100644 arrayfire/array_api/tests/array_object/test_initialization.py create mode 100644 arrayfire/array_api/tests/array_object/test_methods.py create mode 100644 arrayfire/array_api/tests/array_object/test_operators.py delete mode 100644 arrayfire/array_api/tests/test_array_object.py diff --git a/arrayfire/array_api/tests/array_object/__init__.py b/arrayfire/array_api/tests/array_object/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/arrayfire/array_api/tests/array_object/test_initialization.py b/arrayfire/array_api/tests/array_object/test_initialization.py new file mode 100644 index 000000000..4a4426cf6 --- /dev/null +++ b/arrayfire/array_api/tests/array_object/test_initialization.py @@ -0,0 +1,53 @@ +import array as pyarray +import math +from typing import Any, Optional, Tuple + +import pytest + +from arrayfire.array_api.array_object import Array +from arrayfire.array_api.dtypes import Dtype, float32, int16 + +# TODO add tests for array arguments: device, offset, strides +# TODO add tests for all supported dtypes on initialisation +# TODO add test generation + + +@pytest.mark.parametrize( + "array, res_dtype, res_ndim, res_size, res_shape, res_len", [ + (Array(), float32, 0, 0, (), 0), + (Array(dtype=int16), int16, 0, 0, (), 0), + (Array(dtype="short int"), int16, 0, 0, (), 0), + (Array(dtype="h"), int16, 0, 0, (), 0), + (Array(shape=(2, 3)), float32, 2, 6, (2, 3), 2), + (Array([1, 2, 3]), float32, 1, 3, (3,), 3), + (Array(pyarray.array("f", [1, 2, 3])), float32, 1, 3, (3,), 3), + (Array([1], shape=(1,), dtype=float32), float32, 1, 1, (1,), 1), # BUG + (Array(Array([1])), float32, 1, 1, (1,), 1) + ]) +def test_initialization_with_different_arguments( + array: Array, res_dtype: Dtype, res_ndim: int, res_size: int, res_shape: Tuple[int, ...], + res_len: int) -> None: + assert array.dtype == res_dtype + assert array.ndim == res_ndim + assert array.size == res_size + # NOTE math.prod from empty object returns 1, but it should work for other cases + if res_size != 0: + assert array.size == math.prod(res_shape) + assert array.shape == res_shape + assert len(array) == res_len + + +@pytest.mark.parametrize( + "array_object, dtype, shape", [ + (None, "hello world", ()), + ([[1, 2, 3], [1, 2, 3]], None, ()), + (1, None, ()), + (1, None, (1,)), + ((5, 5), None, ()), + ({1: 2, 3: 4}, None, ()) + ] +) +def test_initalization_with_unsupported_argument_types( + array_object: Any, dtype: Optional[Dtype], shape: Tuple[int, ...]) -> None: + with pytest.raises(TypeError): + Array(x=array_object, dtype=dtype, shape=shape) diff --git a/arrayfire/array_api/tests/array_object/test_methods.py b/arrayfire/array_api/tests/array_object/test_methods.py new file mode 100644 index 000000000..306ad5d53 --- /dev/null +++ b/arrayfire/array_api/tests/array_object/test_methods.py @@ -0,0 +1,31 @@ +from arrayfire.array_api.array_object import Array + +# TODO add more tests for different dtypes + + +def test_array_getitem() -> None: + array = Array([1, 2, 3, 4, 5]) + + int_item = array[2] + assert array.dtype == int_item.dtype + assert int_item.scalar() == 3 + + +def test_scalar() -> None: + array = Array([1, 2, 3]) + assert array[1].scalar() == 2 + + +def test_scalar_is_empty() -> None: + array = Array() + assert array.scalar() is None + + +def test_array_to_list() -> None: + array = Array([1, 2, 3]) + assert array.to_list() == [1, 2, 3] + + +def test_array_to_list_is_empty() -> None: + array = Array() + assert array.to_list() == [] diff --git a/arrayfire/array_api/tests/array_object/test_operators.py b/arrayfire/array_api/tests/array_object/test_operators.py new file mode 100644 index 000000000..f3d3a2081 --- /dev/null +++ b/arrayfire/array_api/tests/array_object/test_operators.py @@ -0,0 +1,80 @@ +import operator +from typing import Any, List, Union + +import pytest + +from arrayfire.array_api.array_object import Array + + +# HACK replace for e.g. abs(x1-x2) < 1e-6 ~ https://davidamos.dev/the-right-way-to-compare-floats-in-python/ +def _round(list_: List[Union[int, float]], symbols: int = 4) -> List[Union[int, float]]: + return [round(x, symbols) for x in list_] + + +def pytest_generate_tests(metafunc: Any) -> None: + if "array_origin" in metafunc.fixturenames: + metafunc.parametrize("array_origin", [ + [1, 2, 3], + # [4.2, 7.5, 5.41] # FIXME too big difference between python pow and af backend + ]) + if "op_origin" in metafunc.fixturenames: + metafunc.parametrize("op_origin", [ + "add", # __add__, __iadd__, __radd__ + "sub", # __sub__, __isub__, __rsub__ + "mul", # __mul__, __imul__, __rmul__ + "truediv", # __truediv__, __itruediv__, __rtruediv__ + # "floordiv", # __floordiv__, __ifloordiv__, __rfloordiv__ # TODO + "mod", # __mod__, __imod__, __rmod__ + "pow" # __pow__, __ipow__, __rpow__, + ]) + if "operand" in metafunc.fixturenames: + metafunc.parametrize("operand", [ + 2, + 1.5, + [9, 9, 9], + ]) + if "false_operand" in metafunc.fixturenames: + metafunc.parametrize("false_operand", [ + (1, 2, 3), + ("2"), + {2.34, 523.2}, + "15" + ]) + + +def test_arithmetic_operators( + array_origin: List[Union[int, float]], op_origin: str, + operand: Union[int, float, List[Union[int, float]]]) -> None: + op = getattr(operator, op_origin) + iop = getattr(operator, "i" + op_origin) + + if isinstance(operand, list): + ref = [op(x, y) for x, y in zip(array_origin, operand)] + rref = [op(y, x) for x, y in zip(array_origin, operand)] + operand = Array(operand) # type: ignore[assignment] + else: + ref = [op(x, operand) for x in array_origin] + rref = [op(operand, x) for x in array_origin] + + array = Array(array_origin) + + res = op(array, operand) + ires = iop(array, operand) + rres = op(operand, array) + + assert _round(res.to_list()) == _round(ires.to_list()) == _round(ref) + assert _round(rres.to_list()) == _round(rref) + + assert res.dtype == ires.dtype == rres.dtype + assert res.ndim == ires.ndim == rres.ndim + assert res.size == ires.size == ires.size + assert res.shape == ires.shape == rres.shape + assert len(res) == len(ires) == len(rres) + + +def test_arithmetic_operators_expected_to_raise_error( + array_origin: List[Union[int, float]], op_origin: str, false_operand: Any) -> None: + array = Array(array_origin) + op = getattr(operator, op_origin) + with pytest.raises(TypeError): + op(array, false_operand) diff --git a/arrayfire/array_api/tests/test_array_object.py b/arrayfire/array_api/tests/test_array_object.py deleted file mode 100644 index 5f66a9cab..000000000 --- a/arrayfire/array_api/tests/test_array_object.py +++ /dev/null @@ -1,459 +0,0 @@ -import array as pyarray -import math -from typing import Any - -import pytest - -from arrayfire.array_api.array_object import Array -from arrayfire.array_api.dtypes import float32, int16, supported_dtypes - -# TODO change separated methods with setup and teardown to avoid code duplication -# TODO add tests for array arguments: device, offset, strides -# TODO add tests for all supported dtypes on initialisation -# TODO check if e.g. abs(x1-x2) < 1e-6 ~ https://davidamos.dev/the-right-way-to-compare-floats-in-python/ - - -def test_create_empty_array() -> None: - array = Array() - - assert array.dtype == float32 - assert array.ndim == 0 - assert array.size == 0 - assert array.shape == () - assert len(array) == 0 - - -def test_create_empty_array_with_nonempty_dtype() -> None: - array = Array(dtype=int16) - - assert array.dtype == int16 - assert array.ndim == 0 - assert array.size == 0 - assert array.shape == () - assert len(array) == 0 - - -def test_create_empty_array_with_str_dtype() -> None: - array = Array(dtype="short int") - - assert array.dtype == int16 - assert array.ndim == 0 - assert array.size == 0 - assert array.shape == () - assert len(array) == 0 - - -def test_create_empty_array_with_literal_dtype() -> None: - array = Array(dtype="h") - - assert array.dtype == int16 - assert array.ndim == 0 - assert array.size == 0 - assert array.shape == () - assert len(array) == 0 - - -def test_create_empty_array_with_not_matching_str_dtype() -> None: - with pytest.raises(TypeError): - Array(dtype="hello world") - - -def test_create_empty_array_with_nonempty_shape() -> None: - array = Array(shape=(2, 3)) - - assert array.dtype == float32 - assert array.ndim == 2 - assert array.size == math.prod(array.shape) == 6 - assert array.shape == (2, 3) - assert len(array) == 2 - - -def test_create_array_from_1d_list() -> None: - array = Array([1, 2, 3]) - - assert array.dtype == float32 - assert array.ndim == 1 - assert array.size == math.prod(array.shape) == 3 - assert array.shape == (3,) - assert len(array) == 3 - - -def test_create_array_from_2d_list() -> None: - with pytest.raises(TypeError): - Array([[1, 2, 3], [1, 2, 3]]) # type: ignore[list-item] - - -def test_create_array_from_pyarray() -> None: - py_array = pyarray.array("f", [1, 2, 3]) - array = Array(py_array) - - assert array.dtype == float32 - assert array.ndim == 1 - assert array.size == math.prod(array.shape) == 3 - assert array.shape == (3,) - assert len(array) == 3 - - -def test_array_from_list_with_unsupported_dtype() -> None: - for dtype in supported_dtypes: - if dtype == float32: - continue - with pytest.raises(TypeError): - Array([1, 2, 3], dtype=dtype) - - -def test_array_from_af_array() -> None: - array1 = Array([1]) - array2 = Array(array1) - - assert array1.dtype == array2.dtype == float32 - assert array1.ndim == array2.ndim == 1 - assert array1.size == array2.size == math.prod(array1.shape) == math.prod(array2.shape) == 1 - assert array1.shape == array2.shape == (1,) - assert len(array1) == len(array2) == 1 - - -def test_array_from_int_without_shape() -> None: - with pytest.raises(TypeError): - Array(1) - - -def test_array_from_int_without_dtype() -> None: - with pytest.raises(TypeError): - Array(1, shape=(1,)) - -# def test_array_from_int_with_parameters() -> None: # BUG seg fault -# array = Array(1, shape=(1,), dtype=float32) - -# assert array.dtype == float32 -# assert array.ndim == 1 -# assert array.size == 1 -# assert array.shape == (1,) -# assert len(array) == 1 - - -def test_array_from_unsupported_type() -> None: - with pytest.raises(TypeError): - Array((5, 5)) # type: ignore[arg-type] - - with pytest.raises(TypeError): - Array({1: 2, 3: 4}) # type: ignore[arg-type] - - -def test_array_getitem() -> None: - array = Array([1, 2, 3, 4, 5]) - - int_item = array[2] - assert array.dtype == int_item.dtype - assert int_item.scalar() == 3 - - # TODO add more tests for different dtypes - - -def test_scalar() -> None: - array = Array([1, 2, 3]) - assert array[1].scalar() == 2 - - -def test_scalar_is_empty() -> None: - array = Array() - assert array.scalar() is None - - -def test_array_to_list() -> None: - array = Array([1, 2, 3]) - assert array.to_list() == [1, 2, 3] - - -def test_array_to_list_is_empty() -> None: - array = Array() - assert array.to_list() == [] - - -class TestClassArithmeticOperators: - def setup_method(self, method: Any) -> None: - self.list = [1, 2, 3] - self.const_int = 2 - self.const_float = 1.5 - self.array = Array(self.list) # FIXME typing - self.array_other = Array([9, 9, 9]) - - self.tuple = (1, 2, 3) - self.const_str = "15" - - def test_add_int(self) -> None: - res = self.array + self.const_int - assert res[0].scalar() == 3 - assert res[1].scalar() == 4 - assert res[2].scalar() == 5 - - # Test __add__, __iadd__, __radd__ - - def test_add_float(self) -> None: - res = self.array + self.const_float - assert res[0].scalar() == 2.5 - assert res[1].scalar() == 3.5 - assert res[2].scalar() == 4.5 - - def test_add_array(self) -> None: - res = self.array + self.array_other - assert res[0].scalar() == 10 - assert res[1].scalar() == 11 - assert res[2].scalar() == 12 - - def test_add_inplace_and_reflected(self) -> None: - res = self.array + self.const_int - ires = self.array - ires += self.const_int - rres = self.const_int + self.array # type: ignore[operator] - - assert res[0].scalar() == ires[0].scalar() == rres[0].scalar() == 3 - assert res[1].scalar() == ires[1].scalar() == rres[1].scalar() == 4 - assert res[2].scalar() == ires[2].scalar() == rres[2].scalar() == 5 - - assert res.dtype == ires.dtype == rres.dtype - assert res.ndim == ires.ndim == rres.ndim - assert res.size == ires.size == ires.size - assert res.shape == ires.shape == rres.shape - assert len(res) == len(ires) == len(rres) - - def test_add_raises_type_error(self) -> None: - with pytest.raises(TypeError): - self.array + self.const_str # type: ignore[operator] - - with pytest.raises(TypeError): - self.array + self.tuple # type: ignore[operator] - - # Test __sub__, __isub__, __rsub__ - - def test_sub_int(self) -> None: - res = self.array - self.const_int - assert res[0].scalar() == -1 - assert res[1].scalar() == 0 - assert res[2].scalar() == 1 - - def test_sub_float(self) -> None: - res = self.array - self.const_float - assert res[0].scalar() == -0.5 - assert res[1].scalar() == 0.5 - assert res[2].scalar() == 1.5 - - def test_sub_arr(self) -> None: - res = self.array - self.array_other - assert res[0].scalar() == -8 - assert res[1].scalar() == -7 - assert res[2].scalar() == -6 - - def test_sub_inplace_and_reflected(self) -> None: - res = self.array - self.const_int - ires = self.array - ires -= self.const_int - rres = self.const_int - self.array # type: ignore[operator] - - assert res[0].scalar() == ires[0].scalar() == -1 - assert res[1].scalar() == ires[1].scalar() == 0 - assert res[2].scalar() == ires[2].scalar() == 1 - - assert rres[0].scalar() == 1 - assert rres[1].scalar() == 0 - assert rres[2].scalar() == -1 - - assert res.dtype == ires.dtype == rres.dtype - assert res.ndim == ires.ndim == rres.ndim - assert res.size == ires.size == ires.size - assert res.shape == ires.shape == rres.shape - assert len(res) == len(ires) == len(rres) - - def test_sub_raises_type_error(self) -> None: - with pytest.raises(TypeError): - self.array - self.const_str # type: ignore[operator] - - with pytest.raises(TypeError): - self.array - self.tuple # type: ignore[operator] - - # Test __mul__, __imul__, __rmul__ - - def test_mul_int(self) -> None: - res = self.array * self.const_int - assert res[0].scalar() == 2 - assert res[1].scalar() == 4 - assert res[2].scalar() == 6 - - def test_mul_float(self) -> None: - res = self.array * self.const_float - assert res[0].scalar() == 1.5 - assert res[1].scalar() == 3 - assert res[2].scalar() == 4.5 - - def test_mul_array(self) -> None: - res = self.array * self.array_other - assert res[0].scalar() == 9 - assert res[1].scalar() == 18 - assert res[2].scalar() == 27 - - def test_mul_inplace_and_reflected(self) -> None: - res = self.array * self.const_int - ires = self.array - ires *= self.const_int - rres = self.const_int * self.array # type: ignore[operator] - - assert res[0].scalar() == ires[0].scalar() == rres[0].scalar() == 2 - assert res[1].scalar() == ires[1].scalar() == rres[1].scalar() == 4 - assert res[2].scalar() == ires[2].scalar() == rres[2].scalar() == 6 - - assert res.dtype == ires.dtype == rres.dtype - assert res.ndim == ires.ndim == rres.ndim - assert res.size == ires.size == ires.size - assert res.shape == ires.shape == rres.shape - assert len(res) == len(ires) == len(rres) - - def test_mul_raises_type_error(self) -> None: - with pytest.raises(TypeError): - self.array * self.const_str # type: ignore[operator] - - with pytest.raises(TypeError): - self.array * self.tuple # type: ignore[operator] - - # Test __truediv__, __itruediv__, __rtruediv__ - - def test_truediv_int(self) -> None: - res = self.array / self.const_int - assert res[0].scalar() == 0.5 - assert res[1].scalar() == 1 - assert res[2].scalar() == 1.5 - - def test_truediv_float(self) -> None: - res = self.array / self.const_float - assert round(res[0].scalar(), 5) == 0.66667 # type: ignore[arg-type] - assert round(res[1].scalar(), 5) == 1.33333 # type: ignore[arg-type] - assert res[2].scalar() == 2 - - def test_truediv_array(self) -> None: - res = self.array / self.array_other - assert round(res[0].scalar(), 5) == 0.11111 # type: ignore[arg-type] - assert round(res[1].scalar(), 5) == 0.22222 # type: ignore[arg-type] - assert round(res[2].scalar(), 5) == 0.33333 # type: ignore[arg-type] - - def test_truediv_inplace_and_reflected(self) -> None: - res = self.array / self.const_int - ires = self.array - ires /= self.const_int - rres = self.const_int / self.array # type: ignore[operator] - - assert res[0].scalar() == ires[0].scalar() == 0.5 - assert res[1].scalar() == ires[1].scalar() == 1 - assert res[2].scalar() == ires[2].scalar() == 1.5 - - assert rres[0].scalar() == 2 - assert rres[1].scalar() == 1 - assert round(rres[2].scalar(), 5) == 0.66667 - - assert res.dtype == ires.dtype == rres.dtype - assert res.ndim == ires.ndim == rres.ndim - assert res.size == ires.size == ires.size - assert res.shape == ires.shape == rres.shape - assert len(res) == len(ires) == len(rres) - - def test_truediv_raises_type_error(self) -> None: - with pytest.raises(TypeError): - self.array / self.const_str # type: ignore[operator] - - with pytest.raises(TypeError): - self.array / self.tuple # type: ignore[operator] - - # TODO - # Test __floordiv__, __ifloordiv__, __rfloordiv__ - - # Test __mod__, __imod__, __rmod__ - - def test_mod_int(self) -> None: - res = self.array % self.const_int - assert res[0].scalar() == 1 - assert res[1].scalar() == 0 - assert res[2].scalar() == 1 - - def test_mod_float(self) -> None: - res = self.array % self.const_float - assert res[0].scalar() == 1.0 - assert res[1].scalar() == 0.5 - assert res[2].scalar() == 0.0 - - def test_mod_array(self) -> None: - res = self.array % self.array_other - assert res[0].scalar() == 1.0 - assert res[1].scalar() == 2.0 - assert res[2].scalar() == 3.0 - - def test_mod_inplace_and_reflected(self) -> None: - res = self.array % self.const_int - ires = self.array - ires %= self.const_int - rres = self.const_int % self.array # type: ignore[operator] - - assert res[0].scalar() == ires[0].scalar() == 1 - assert res[1].scalar() == ires[1].scalar() == 0 - assert res[2].scalar() == ires[2].scalar() == 1 - - assert rres[0].scalar() == 0 - assert rres[1].scalar() == 0 - assert rres[2].scalar() == 2 - - assert res.dtype == ires.dtype == rres.dtype - assert res.ndim == ires.ndim == rres.ndim - assert res.size == ires.size == ires.size - assert res.shape == ires.shape == rres.shape - assert len(res) == len(ires) == len(rres) - - def test_mod_raises_type_error(self) -> None: - with pytest.raises(TypeError): - self.array % self.const_str # type: ignore[operator] - - with pytest.raises(TypeError): - self.array % self.tuple # type: ignore[operator] - - # Test __pow__, __ipow__, __rpow__ - - def test_pow_int(self) -> None: - res = self.array ** self.const_int - assert res[0].scalar() == 1 - assert res[1].scalar() == 4 - assert res[2].scalar() == 9 - - def test_pow_float(self) -> None: - res = self.array ** self.const_float - assert res[0].scalar() == 1 - assert round(res[1].scalar(), 5) == 2.82843 # type: ignore[arg-type] - assert round(res[2].scalar(), 5) == 5.19615 # type: ignore[arg-type] - - def test_pow_array(self) -> None: - res = self.array ** self.array_other - assert res[0].scalar() == 1 - assert res[1].scalar() == 512 - assert res[2].scalar() == 19683 - - def test_pow_inplace_and_reflected(self) -> None: - res = self.array ** self.const_int - ires = self.array - ires **= self.const_int - rres = self.const_int ** self.array # type: ignore[operator] - - assert res[0].scalar() == ires[0].scalar() == 1 - assert res[1].scalar() == ires[1].scalar() == 4 - assert res[2].scalar() == ires[2].scalar() == 9 - - assert rres[0].scalar() == 2 - assert rres[1].scalar() == 4 - assert rres[2].scalar() == 8 - - assert res.dtype == ires.dtype == rres.dtype - assert res.ndim == ires.ndim == rres.ndim - assert res.size == ires.size == ires.size - assert res.shape == ires.shape == rres.shape - assert len(res) == len(ires) == len(rres) - - def test_pow_raises_type_error(self) -> None: - with pytest.raises(TypeError): - self.array % self.const_str # type: ignore[operator] - - with pytest.raises(TypeError): - self.array % self.tuple # type: ignore[operator] From 51f6efde2d4bf3c8b9aaa8472c4da72f7555181f Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 14 Feb 2023 00:33:47 +0200 Subject: [PATCH 35/36] Add comparison operators tests --- .../tests/array_object/test_operators.py | 61 ++++++++++++++++--- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/arrayfire/array_api/tests/array_object/test_operators.py b/arrayfire/array_api/tests/array_object/test_operators.py index f3d3a2081..fc4be3a47 100644 --- a/arrayfire/array_api/tests/array_object/test_operators.py +++ b/arrayfire/array_api/tests/array_object/test_operators.py @@ -1,13 +1,16 @@ import operator -from typing import Any, List, Union +from typing import Any, Callable, List, Union import pytest from arrayfire.array_api.array_object import Array +from arrayfire.array_api.dtypes import bool as af_bool + +Operator = Callable[[Union[int, float, Array], Union[int, float, Array]], Array] -# HACK replace for e.g. abs(x1-x2) < 1e-6 ~ https://davidamos.dev/the-right-way-to-compare-floats-in-python/ def _round(list_: List[Union[int, float]], symbols: int = 4) -> List[Union[int, float]]: + # HACK replace for e.g. abs(x1-x2) < 1e-6 ~ https://davidamos.dev/the-right-way-to-compare-floats-in-python/ return [round(x, symbols) for x in list_] @@ -17,15 +20,29 @@ def pytest_generate_tests(metafunc: Any) -> None: [1, 2, 3], # [4.2, 7.5, 5.41] # FIXME too big difference between python pow and af backend ]) - if "op_origin" in metafunc.fixturenames: - metafunc.parametrize("op_origin", [ + if "arithmetic_operator" in metafunc.fixturenames: + metafunc.parametrize("arithmetic_operator", [ "add", # __add__, __iadd__, __radd__ "sub", # __sub__, __isub__, __rsub__ "mul", # __mul__, __imul__, __rmul__ "truediv", # __truediv__, __itruediv__, __rtruediv__ # "floordiv", # __floordiv__, __ifloordiv__, __rfloordiv__ # TODO "mod", # __mod__, __imod__, __rmod__ - "pow" # __pow__, __ipow__, __rpow__, + "pow", # __pow__, __ipow__, __rpow__, + ]) + if "array_operator" in metafunc.fixturenames: + metafunc.parametrize("array_operator", [ + operator.matmul, + operator.imatmul + ]) + if "comparison_operator" in metafunc.fixturenames: + metafunc.parametrize("comparison_operator", [ + operator.lt, + operator.le, + operator.gt, + operator.ge, + operator.eq, + operator.ne ]) if "operand" in metafunc.fixturenames: metafunc.parametrize("operand", [ @@ -43,10 +60,10 @@ def pytest_generate_tests(metafunc: Any) -> None: def test_arithmetic_operators( - array_origin: List[Union[int, float]], op_origin: str, + array_origin: List[Union[int, float]], arithmetic_operator: str, operand: Union[int, float, List[Union[int, float]]]) -> None: - op = getattr(operator, op_origin) - iop = getattr(operator, "i" + op_origin) + op = getattr(operator, arithmetic_operator) + iop = getattr(operator, "i" + arithmetic_operator) if isinstance(operand, list): ref = [op(x, y) for x, y in zip(array_origin, operand)] @@ -73,8 +90,32 @@ def test_arithmetic_operators( def test_arithmetic_operators_expected_to_raise_error( - array_origin: List[Union[int, float]], op_origin: str, false_operand: Any) -> None: + array_origin: List[Union[int, float]], arithmetic_operator: str, false_operand: Any) -> None: array = Array(array_origin) - op = getattr(operator, op_origin) + op = getattr(operator, arithmetic_operator) with pytest.raises(TypeError): op(array, false_operand) + + +def test_comparison_operators( + array_origin: List[Union[int, float]], comparison_operator: Operator, + operand: Union[int, float, List[Union[int, float]]]) -> None: + if isinstance(operand, list): + ref = [comparison_operator(x, y) for x, y in zip(array_origin, operand)] + operand = Array(operand) # type: ignore[assignment] + else: + ref = [comparison_operator(x, operand) for x in array_origin] + + array = Array(array_origin) + res = comparison_operator(array, operand) # type: ignore[arg-type] + + assert res.to_list() == ref + assert res.dtype == af_bool + + +def test_comparison_operators_expected_to_raise_error( + array_origin: List[Union[int, float]], comparison_operator: Operator, false_operand: Any) -> None: + array = Array(array_origin) + + with pytest.raises(TypeError): + comparison_operator(array, false_operand) From 23e2635698e26fb170fc933341fb251c43d95b1b Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 21 Feb 2023 20:25:03 +0200 Subject: [PATCH 36/36] Minor fixes for tests --- arrayfire/array_api/array_object.py | 1 + arrayfire/array_api/pytest.ini | 2 +- .../tests/array_object/test_operators.py | 57 ++++++++++--------- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/arrayfire/array_api/array_object.py b/arrayfire/array_api/array_object.py index cdae8da94..5b9ce4c7f 100644 --- a/arrayfire/array_api/array_object.py +++ b/arrayfire/array_api/array_object.py @@ -933,6 +933,7 @@ def _process_c_function(lhs: Union[int, float, Array], rhs: Union[int, float, Ar rhs_array = rhs.arr else: + # FIXME in reflected operators this exception shows wrong error message raise TypeError(f"{type(rhs)} is not supported and can not be passed to C binary function.") out.arr = c_function(lhs_array, rhs_array) diff --git a/arrayfire/array_api/pytest.ini b/arrayfire/array_api/pytest.ini index 7fd828bec..bd20cb706 100644 --- a/arrayfire/array_api/pytest.ini +++ b/arrayfire/array_api/pytest.ini @@ -1,4 +1,4 @@ [pytest] -addopts = --cache-clear --cov=./arrayfire/array_api --flake8 --isort -s ./arrayfire/array_api +addopts = --cache-clear --cov=./arrayfire/array_api --flake8 --isort -srx ./arrayfire/array_api console_output_style = classic markers = mypy diff --git a/arrayfire/array_api/tests/array_object/test_operators.py b/arrayfire/array_api/tests/array_object/test_operators.py index fc4be3a47..9403f1937 100644 --- a/arrayfire/array_api/tests/array_object/test_operators.py +++ b/arrayfire/array_api/tests/array_object/test_operators.py @@ -6,7 +6,18 @@ from arrayfire.array_api.array_object import Array from arrayfire.array_api.dtypes import bool as af_bool -Operator = Callable[[Union[int, float, Array], Union[int, float, Array]], Array] +Operator = Callable[..., Any] + +arithmetic_operators = [ + [operator.add, operator.iadd], + [operator.sub, operator.isub], + [operator.mul, operator.imul], + [operator.truediv, operator.itruediv], + [operator.mod, operator.imod], + [operator.pow, operator.ipow] +] + +comparison_operators = [operator.lt, operator.le, operator.gt, operator.ge, operator.eq, operator.ne] def _round(list_: List[Union[int, float]], symbols: int = 4) -> List[Union[int, float]]: @@ -21,29 +32,9 @@ def pytest_generate_tests(metafunc: Any) -> None: # [4.2, 7.5, 5.41] # FIXME too big difference between python pow and af backend ]) if "arithmetic_operator" in metafunc.fixturenames: - metafunc.parametrize("arithmetic_operator", [ - "add", # __add__, __iadd__, __radd__ - "sub", # __sub__, __isub__, __rsub__ - "mul", # __mul__, __imul__, __rmul__ - "truediv", # __truediv__, __itruediv__, __rtruediv__ - # "floordiv", # __floordiv__, __ifloordiv__, __rfloordiv__ # TODO - "mod", # __mod__, __imod__, __rmod__ - "pow", # __pow__, __ipow__, __rpow__, - ]) - if "array_operator" in metafunc.fixturenames: - metafunc.parametrize("array_operator", [ - operator.matmul, - operator.imatmul - ]) + metafunc.parametrize("arithmetic_operator", arithmetic_operators) if "comparison_operator" in metafunc.fixturenames: - metafunc.parametrize("comparison_operator", [ - operator.lt, - operator.le, - operator.gt, - operator.ge, - operator.eq, - operator.ne - ]) + metafunc.parametrize("comparison_operator", comparison_operators) if "operand" in metafunc.fixturenames: metafunc.parametrize("operand", [ 2, @@ -60,10 +51,10 @@ def pytest_generate_tests(metafunc: Any) -> None: def test_arithmetic_operators( - array_origin: List[Union[int, float]], arithmetic_operator: str, + array_origin: List[Union[int, float]], arithmetic_operator: List[Operator], operand: Union[int, float, List[Union[int, float]]]) -> None: - op = getattr(operator, arithmetic_operator) - iop = getattr(operator, "i" + arithmetic_operator) + op = arithmetic_operator[0] + iop = arithmetic_operator[1] if isinstance(operand, list): ref = [op(x, y) for x, y in zip(array_origin, operand)] @@ -90,10 +81,20 @@ def test_arithmetic_operators( def test_arithmetic_operators_expected_to_raise_error( - array_origin: List[Union[int, float]], arithmetic_operator: str, false_operand: Any) -> None: + array_origin: List[Union[int, float]], arithmetic_operator: List[Operator], false_operand: Any) -> None: array = Array(array_origin) - op = getattr(operator, arithmetic_operator) + + with pytest.raises(TypeError): + op = arithmetic_operator[0] + op(array, false_operand) + + # BUG string type false operand never raises an error + # with pytest.raises(TypeError): + # op = arithmetic_operator[0] + # op(false_operand, array) + with pytest.raises(TypeError): + op = arithmetic_operator[1] op(array, false_operand)