diff --git a/.flake8 b/.flake8 index 0d726f9..2760c29 100755 --- a/.flake8 +++ b/.flake8 @@ -8,4 +8,6 @@ max-line-length = 119 exclude = .venv/** build/** - benchmarks/** \ No newline at end of file + benchmarks/** + docs/* + tests/test_documentation/test_documentation.py diff --git a/README.md b/README.md index 81a3f90..decf586 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,107 @@ -# arrayfire-py - -Arrayfire python wrapper - -## Coverage - -- [x] Computer Vision -- [] Events -- [x] Functions to Create and Modify Arrays -- [x] Functions to Work with Internal Array Layout -- [x] Image Processing - - [x] Features -- [x] Input and Output Functions -- [x] Interface Functions -- [x] Linear Algebra -- [x] Machine Learning -- [x] Mathematical Functions -- [x] Signal Processing -- [x] Statistics -- [x] Unified API Functions -- [x] Vector Algorithms +# arrayfire-python (WIP) +

+ +[ArrayFire](https://github.com/arrayfire/arrayfire) is a high performance library for parallel computing with an easy-to-use API. It enables users to write scientific computing code that is portable across CUDA, OpenCL and CPU devices. + +This project is a **work in progress**. It is meant to provide a numpy-like Python interface for the ArrayFire C library, i.e, it provides array functionality, math operations, printing, etc. This is the front-end python library for using ArrayFire. It is currently supported on Python 3.10+. + +Here is an example of the library at work: +```py +# Set backend and device (optional: 'cuda', 'opencl', 'oneapi', 'cpu') +af.setBackend(af.BackendType.cuda) +af.setDevice(0) + +# Create two 5x5 arrays on the GPU +a = af.randu((5, 5)) +b = af.randu((5, 5)) + +# Perform element-wise addition and matrix multiplication +c = a + b +d = af.matmul(a, b) + +# Print the result +print(c, "Element-wise Sum") +print(d, "Matrix Product") +``` + +# Installing + +**Requirement Details** +This project is separated into 3 different parts: +``` +arrayfire-py -> arrayfire-binary-python-wrapper -> ArrayFire C Libraries +``` +This means that arrayfire with python each of these parts is needed: +- [`arrayfire-py`](https://github.com/arrayfire/arrayfire-python) is the intended User Interface that provides a numpy-like layer to execute math and array operations with ArrayFire. *** This is the preferred Interface *** +- [`arrayfire-binary-python-wrapper`](https://github.com/arrayfire/arrayfire-binary-python-wrapper) is the wrapper that provides Python direct access to the ArrayFire functions in the C library. This package must have access to ArrayFire binaries. +- [`ArrayFire C Libraries`](https://github.com/arrayfire/arrayfire) are the binaries obtained from compiling the [ArrayFire C/C++ Project](https://github.com/arrayfire/arrayfire). You obtain these easily through [installers in the ArrayFire download page](https://arrayfire.com/download/). + +**Install the last stable version of python wrapper:** +```sh +pip install arrayfire_binary_python_wrapper-0.8.0+af3.10.0-py3-none-linux_x86_64.whl # install required binary wrapper with the 3.10 ArrayFire binaries included +pip install arrayfire-py # install arrayfire python interface library +``` + +**Install a pre-built wheel:** +``` +pip install arrayfire-py -f https://repo.arrayfire.com/python/wheels/arrayfire-python/0.1.0 +``` + +# Building +Building this interface is straight forward using [scikit-build-core](https://github.com/scikit-build/scikit-build-core): +``` +python -m pip install -r dev-requirements.txt +python -m build --wheel +``` + +**Note: Building this project does not require the arrayfire-binary-python-wrapper package; however, the binary wrapper is needed to run any projects with it** + +# Running Tests + +Tests are located in folder [tests](tests). + +To run the tests, use: +```bash +python -m pytest tests/ +``` + +# Contributing + +If you are interested in using ArrayFire through python, we would appreciate any feedback and contributions. + +The community of ArrayFire developers invites you to build with us if you are +interested and able to write top-performing tensor functions. Together we can +fulfill [The ArrayFire +Mission](https://github.com/arrayfire/arrayfire/wiki/The-ArrayFire-Mission-Statement) +for fast scientific computing for all. + +Contributions of any kind are welcome! Please refer to [the +wiki](https://github.com/arrayfire/arrayfire/wiki) and our [Code of +Conduct](33) to learn more about how you can get involved with the ArrayFire +Community through +[Sponsorship](https://github.com/arrayfire/arrayfire/wiki/Sponsorship), +[Developer +Commits](https://github.com/arrayfire/arrayfire/wiki/Contributing-Code-to-ArrayFire), +or [Governance](https://github.com/arrayfire/arrayfire/wiki/Governance). + +# Citations and Acknowledgements + +If you redistribute ArrayFire, please follow the terms established in [the +license](LICENSE). + +ArrayFire development is funded by AccelerEyes LLC and several third parties, +please see the list of [acknowledgements](ACKNOWLEDGEMENTS.md) for an +expression of our gratitude. + +# Support and Contact Info + +* [Slack Chat](https://join.slack.com/t/arrayfire-org/shared_invite/MjI4MjIzMDMzMTczLTE1MDI5ODg4NzYtN2QwNGE3ODA5OQ) +* [Google Groups](https://groups.google.com/forum/#!forum/arrayfire-users) +* ArrayFire Services: [Consulting](http://arrayfire.com/consulting) | [Support](http://arrayfire.com/download) | [Training](http://arrayfire.com/training) + +# Trademark Policy + +The literal mark "ArrayFire" and ArrayFire logos are trademarks of AccelerEyes +LLC (dba ArrayFire). If you wish to use either of these marks in your own +project, please consult [ArrayFire's Trademark +Policy](http://arrayfire.com/trademark-policy/) diff --git a/arrayfire/__init__.py b/arrayfire/__init__.py index 7a5c4a3..9cefbea 100755 --- a/arrayfire/__init__.py +++ b/arrayfire/__init__.py @@ -103,8 +103,6 @@ from arrayfire.library.array_functions import ( constant, - zeros, - ones, copy_array, diag, eval, @@ -119,17 +117,19 @@ lookup, lower, moddims, - reshape, + ones, pad, range, reorder, replace, + reshape, select, set_manual_eval_flag, shift, tile, transpose, upper, + zeros, ) __all__ += ["gloh", "orb", "sift", "dog", "fast", "harris", "susan", "hamming_matcher", "nearest_neighbour"] diff --git a/arrayfire/array_object.py b/arrayfire/array_object.py index 3cb49ea..361eab2 100755 --- a/arrayfire/array_object.py +++ b/arrayfire/array_object.py @@ -758,10 +758,10 @@ def __getitem__(self, key: IndexKey, /) -> Array: ndims = self.ndim indexing = key - - if isinstance(key, int | float | slice): # when indexing with one dimension, treat it as indexing a flat array + + if isinstance(key, int | float | slice): # when indexing with one dimension, treat it as indexing a flat array ndims = 1 - elif isinstance(key, Array): # when indexing with one array, treat it as indexing a flat array + elif isinstance(key, Array): # when indexing with one array, treat it as indexing a flat array ndims = 1 if key.is_bool: indexing = wrapper.where(key.arr) @@ -836,9 +836,9 @@ def __setitem__(self, key: IndexKey, value: int | float | bool | Array, /) -> No del_other = False indexing = key - if isinstance(key, int | float | slice): # when indexing with one dimension, treat it as indexing a flat array + if isinstance(key, int | float | slice): # when indexing with one dimension, treat it as indexing a flat array ndims = 1 - elif isinstance(key, Array): # when indexing with one array, treat it as indexing a flat array + elif isinstance(key, Array): # when indexing with one array, treat it as indexing a flat array ndims = 1 if key.is_bool: indexing = wrapper.where(key.arr) @@ -930,7 +930,8 @@ def T(self) -> Array: Note ---- - - The array instance must be two-dimensional. If the array instance is not two-dimensional, an error should be raised. + - The array instance must be two-dimensional. If the array instance is not two-dimensional, + | an error should be raised. """ if self.ndim < 2: @@ -949,11 +950,13 @@ def H(self) -> Array: ------- Array Two-dimensional array whose first and last dimensions (axes) are permuted in reverse order relative to - original array with its elements complex conjugated. The returned array must have the same data type as the original array. + | original array with its elements complex conjugated. + | 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. + - The array instance must be two-dimensional. If the array instance is not two-dimensional, + | an error should be raised. """ if self.ndim < 2: @@ -1191,7 +1194,8 @@ def reshape(self, shape) -> Array: ------- out : af.Array - An array containing the same data as `array` with the specified shape. - - The total number of elements in `array` must match the product of the dimensions specified in the `shape` tuple. + - The total number of elements in `array` must match the product of the dimensions + | specified in the `shape` tuple. Raises ------ diff --git a/arrayfire/library/array_functions.py b/arrayfire/library/array_functions.py index cc2d9e9..1c905fc 100644 --- a/arrayfire/library/array_functions.py +++ b/arrayfire/library/array_functions.py @@ -73,6 +73,7 @@ def constant(scalar: int | float | complex, shape: tuple[int, ...] = (1,), dtype """ return cast(Array, wrapper.create_constant_array(scalar, shape, dtype)) + def zeros(shape: tuple[int, ...], dtype: Dtype = float32) -> Array: """ Create a multi-dimensional array filled with zeros @@ -100,6 +101,7 @@ def zeros(shape: tuple[int, ...], dtype: Dtype = float32) -> Array: """ return constant(0, shape, dtype) + def ones(shape: tuple[int, ...], dtype: Dtype = float32) -> Array: """ Create a multi-dimensional array filled with ones @@ -127,6 +129,7 @@ def ones(shape: tuple[int, ...], dtype: Dtype = float32) -> Array: """ return constant(1, shape, dtype) + @afarray_as_array def diag(array: Array, /, *, diag_index: int = 0, extract: bool = True) -> Array: """ @@ -311,7 +314,8 @@ def lower(array: Array, /, *, is_unit_diag: bool = False) -> Array: Notes ----- - The function does not alter the elements above the main diagonal; it simply does not include them in the output. - - This function can be useful for mathematical operations that require lower triangular matrices, such as certain types of matrix factorizations. + - This function can be useful for mathematical operations that require lower triangular matrices, + | such as certain types of matrix factorizations. Examples -------- @@ -367,7 +371,8 @@ def upper(array: Array, /, *, is_unit_diag: bool = False) -> Array: Notes ----- - The function does not alter the elements below the main diagonal; it simply does not include them in the output. - - This function can be useful for mathematical operations that require upper triangular matrices, such as certain types of matrix factorizations. + - This function can be useful for mathematical operations that require upper triangular matrices, + | such as certain types of matrix factorizations. Examples -------- @@ -872,6 +877,7 @@ def moddims(array: Array, shape: tuple[int, ...], /) -> Array: # TODO add examples to doc return cast(Array, wrapper.moddims(array.arr, shape)) + def reshape(array: Array, shape: tuple[int, ...], /) -> Array: """ Modify the shape of the array without changing the data layout. @@ -907,6 +913,7 @@ def reshape(array: Array, shape: tuple[int, ...], /) -> Array: """ return moddims(array, shape) + @afarray_as_array def reorder(array: Array, /, *, shape: tuple[int, ...] = (1, 0, 2, 3)) -> Array: """ diff --git a/arrayfire/library/computer_vision.py b/arrayfire/library/computer_vision.py index 2e6de41..647fd9b 100644 --- a/arrayfire/library/computer_vision.py +++ b/arrayfire/library/computer_vision.py @@ -164,7 +164,8 @@ def sift( tuple[Features, Array] A tuple containing: - An ArrayFire Features object encapsulating the detected keypoints. - - An ArrayFire Array containing the corresponding descriptors for each keypoint. The descriptors are 128-dimensional vectors describing the local appearance around each keypoint. + - An ArrayFire Array containing the corresponding descriptors for each keypoint. + | The descriptors are 128-dimensional vectors describing the local appearance around each keypoint. Note ---- diff --git a/arrayfire/library/linear_algebra.py b/arrayfire/library/linear_algebra.py index 67109f2..3decc29 100644 --- a/arrayfire/library/linear_algebra.py +++ b/arrayfire/library/linear_algebra.py @@ -92,7 +92,7 @@ def gemm( rhs_opts: MatProp = MatProp.NONE, alpha: int | float = 1.0, beta: int | float = 0.0, - accum: Array = None + accum: Array = None, ) -> Array: """ Performs BLAS general matrix multiplication (GEMM) on two Array instances. diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 0000000..30894ce --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,8 @@ +# Benchmarks Results +Here are some graphs comparing ArrayFire Python against other packages for some common operations: + +

+

+

+ +These graphs were generated with this benchmark code using the ArrayFire C Libraries v3.10 \ No newline at end of file diff --git a/benchmarks/img/comparison_afcuda_t4.png b/benchmarks/img/comparison_afcuda_t4.png new file mode 100644 index 0000000..e9c90e8 Binary files /dev/null and b/benchmarks/img/comparison_afcuda_t4.png differ diff --git a/benchmarks/img/comparison_afoneapi_b580.png b/benchmarks/img/comparison_afoneapi_b580.png new file mode 100644 index 0000000..6097b60 Binary files /dev/null and b/benchmarks/img/comparison_afoneapi_b580.png differ diff --git a/benchmarks/img/comparison_afopencl_b580.png b/benchmarks/img/comparison_afopencl_b580.png new file mode 100644 index 0000000..5f624fc Binary files /dev/null and b/benchmarks/img/comparison_afopencl_b580.png differ diff --git a/benchmarks/img/comparison_afopencl_t4.png b/benchmarks/img/comparison_afopencl_t4.png new file mode 100644 index 0000000..a8b62a1 Binary files /dev/null and b/benchmarks/img/comparison_afopencl_t4.png differ diff --git a/docs/afjit.py b/docs/afjit.py index de40c05..1557c19 100644 --- a/docs/afjit.py +++ b/docs/afjit.py @@ -5,13 +5,15 @@ # removed, then the execution of this code would be equivalent to the # following function. -import arrayfire as af import time +import arrayfire as af + samples = int(9e8) x = af.randu((samples)) y = af.randu((samples)) + def pi_no_jit(x, y, samples): temp = x * x af.eval(temp) @@ -23,11 +25,13 @@ def pi_no_jit(x, y, samples): af.eval(temp) return 4.0 * af.sum(temp) / samples + def pi_jit(x, y, samples): temp = af.sqrt(x * x + y * y) < 1 af.eval(temp) return 4.0 * af.sum(temp) / samples + # Print device info af.info() @@ -47,4 +51,4 @@ def pi_jit(x, y, samples): end = time.perf_counter() print("no jit:", end - start, res) -# [jit-endsnippet] \ No newline at end of file +# [jit-endsnippet] diff --git a/docs/arrayandmatrixmanipulation.py b/docs/arrayandmatrixmanipulation.py index 55d5f77..db8bd68 100644 --- a/docs/arrayandmatrixmanipulation.py +++ b/docs/arrayandmatrixmanipulation.py @@ -64,11 +64,11 @@ print(a) -moddims_a = af.moddims(a,(2,4)) +moddims_a = af.moddims(a, (2, 4)) print(moddims_a) -moddims_b = af.moddims(a,(len(a),)) +moddims_b = af.moddims(a, (len(a),)) print(moddims_b) # [manipulation4-endsnippet] @@ -79,24 +79,24 @@ import arrayfire as af -a = af.randu((2,2,3,1)) +a = af.randu((2, 2, 3, 1)) print(a) -a_reorder = af.reorder(a,()) +a_reorder = af.reorder(a, ()) # [manipulation5-endsnippet] # [manipulation6-snippet] import arrayfire as af -a = af.randu((3,5)) +a = af.randu((3, 5)) print(a) -a_shift = af.shift(a,(0,2)) +a_shift = af.shift(a, (0, 2)) print(a_shift) -a_shift1 = af.shift(a,(-1,2)) +a_shift1 = af.shift(a, (-1, 2)) print(a_shift1) # [manipulation6-endsnippet] @@ -106,17 +106,17 @@ import arrayfire as af -a = af.randu((3,)) #[3,1,1,1] +a = af.randu((3,)) # [3,1,1,1] -print (a) +print(a) -a_tile = af.tile(a,(2,)) +a_tile = af.tile(a, (2,)) print(a_tile) -a_tile1 = af.tile(a,(2,2)) +a_tile1 = af.tile(a, (2, 2)) print(a_tile1) -a_tile2 = af.tile(a,(1,2,3)) +a_tile2 = af.tile(a, (1, 2, 3)) print(a_tile2) # [manipulation7-endsnippet] @@ -125,18 +125,18 @@ import arrayfire as af -a = af.randu((3,3)) -print(a) #[3 3 1 1] +a = af.randu((3, 3)) +print(a) # [3 3 1 1] -''' 0.3949 0.8465 0.3709 +""" 0.3949 0.8465 0.3709 0.3561 0.9399 0.2751 - 0.6097 0.6802 0.2720''' - + 0.6097 0.6802 0.2720""" + a_transpose = af.transpose(a) -print(a_transpose) #[3 3 1 1] +print(a_transpose) # [3 3 1 1] -''' 0.3949 0.3561 0.6097 +""" 0.3949 0.3561 0.6097 0.8465 0.9399 0.6802 - 0.3709 0.2751 0.2720''' + 0.3709 0.2751 0.2720""" # [manipulation8-endsnippet] diff --git a/docs/conf.py b/docs/conf.py index ca827f1..4d7bd35 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,47 +2,47 @@ # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html -import sys import os -sys.path.insert(0, os.path.abspath('..')) +import sys + +sys.path.insert(0, os.path.abspath("..")) # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = 'ArrayFire' -copyright = '2025, ArrayFire' -author = 'ArrayFire' -release = '' +project = "ArrayFire" +copyright = "2025, ArrayFire" +author = "ArrayFire" +release = "" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ - 'sphinx.ext.duration', - 'sphinx.ext.doctest', - 'sphinx.ext.autodoc', - 'sphinx.ext.mathjax', - 'sphinx_collapse', + "sphinx.ext.duration", + "sphinx.ext.doctest", + "sphinx.ext.autodoc", + "sphinx.ext.mathjax", + "sphinx_collapse", ] -templates_path = ['_templates'] +templates_path = ["_templates"] exclude_patterns = [] - # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'sphinxawesome_theme' -html_static_path = ['_static'] +html_theme = "sphinxawesome_theme" +html_static_path = ["_static"] html_permalinks = False html_theme_options = { "logo_light": "_static/../images/arrayfire_icon.png", - "logo_dark": "_static/../images/arrayfire_icon.png" + "logo_dark": "_static/../images/arrayfire_icon.png", } # -- Suppress specific warnings -------------------------------------------- suppress_warnings = [ - 'ref.include_missing', + "ref.include_missing", ] diff --git a/docs/gettingstarted.py b/docs/gettingstarted.py index 7d6a092..5ef6e01 100644 --- a/docs/gettingstarted.py +++ b/docs/gettingstarted.py @@ -1,73 +1,72 @@ - # [gettingstarted1-snippet] # Arrays may be created using the array constructor and dimensioned # as 1D, 2D, 3D; however, the values in these arrays will be undefined import arrayfire as af -array = af.constant(0,(100,)) +array = af.constant(0, (100,)) array_2d = af.constant(0, (10, 100)) array_3d = af.constant(0, (10, 10, 10)) # [gettingstarted1-endsnippet] - # [gettingstarted2-snippet] import arrayfire as af # Generate an array of size three filled with zeros. # If no data type is specified, ArrayFire defaults to f32. # The constant function generates the data on the device. -zeroes = af.constant(0,(3,)) +zeroes = af.constant(0, (3,)) # Generate a 1x4 array of uniformly distributed [0,1] random numbers # The randu function generates the data on the device. -rand1 = af.randu((1,4)) +rand1 = af.randu((1, 4)) # Generate a 2x2 array (or matrix, if you prefer) of random numbers # sampled from a normal distribution. # The randn function generates data on the device. -rand2 = af.randu((2,2)) +rand2 = af.randu((2, 2)) # Generate a 3x3 identity matrix. The data is generated on the device. -iden = af.identity((3,3)) +iden = af.identity((3, 3)) # Lastly, create a 2x1 array (column vector) of uniformly distributed # 32-bit complex numbers (c32 data type): -randcplx = af.randu((2,1)) +randcplx = af.randu((2, 1)) # [gettingstarted2-endsnippet] # [gettingstarted3-snippet] import arrayfire as af + # Create a six-element array on the host -hA = ([0, 1, 2, 3, 4, 5]) +hA = [0, 1, 2, 3, 4, 5] # Which can be copied into an ArrayFire Array using the pointer copy # constructor. Here we copy the data into a 2x3 matrix: -A = af.moddims(af.Array(hA),(2,3)) +A = af.moddims(af.Array(hA), (2, 3)) # ArrayFire provides a convenince function for printing array # objects in case you wish to see how the data is stored: print(A) -#todo how to create complex numbers +# todo how to create complex numbers # [gettingstarted3-endsnippet] - # [gettingstarted4-snippet] -import arrayfire as af -import pycuda.driver as cuda import numpy as np +import pycuda.driver as cuda + +import arrayfire as af # Create an array on the host host_ptr = af.Array([0, 1, 2, 3, 4, 5]) # Create an ArrayFire array 'a' from host_ptr (2x3 matrix) -A = af.moddims(host_ptr,(2,3)) +A = af.moddims(host_ptr, (2, 3)) # Allocate CUDA device memory and copy data from host to device device_ptr = cuda.mem_alloc(host_ptr.nbytes) @@ -83,19 +82,18 @@ # [gettingstarted4-endsnippet] - # [gettingstarted5-snippet] import arrayfire as af # Generate two arrays -a= af.randu((2,2)) # Create a 2x2 array with random numbers between [0, 1] -b = af.constant(1,(2,1)) # Create a 2x1 array filled with constant value 1 +a = af.randu((2, 2)) # Create a 2x2 array with random numbers between [0, 1] +b = af.constant(1, (2, 1)) # Create a 2x1 array filled with constant value 1 # Print arrays 'a' and 'b' to the console print("Array 'a':", a) -print("Array 'b':",b) +print("Array 'b':", b) # Print the results of an expression involving arrays result = a.col(0) + b + 0.4 # Perform operation: first column of 'a' + 'b' + 0.4 @@ -109,7 +107,7 @@ import arrayfire as af # Create a 4x5x2 array of uniformly distributed random numbers -a = af.randu((4,5,2)) +a = af.randu((4, 5, 2)) # Determine the number of dimensions using the `numdims()` function print("numdims(a):", a.numdims()) # Print the number of dimensions (should be 3) @@ -150,7 +148,7 @@ # Generate a 3x3 array of uniformly distributed random numbers R = af.randu((3, 3)) -print(af.constant(1,( 3, 3)) + af.join(af.sin(R))) # will be c32 +print(af.constant(1, (3, 3)) + af.join(af.sin(R))) # will be c32 # Rescale complex values to unit circle a = af.randn(5) @@ -158,7 +156,7 @@ # Calculate L2 norm of vectors X = af.randn((3, 4)) -print(af.sqrt(af.sum(af.pow(X, 2)))) # norm of every column vector +print(af.sqrt(af.sum(af.pow(X, 2)))) # norm of every column vector print(af.sqrt(af.sum(af.pow(X, 2), 0))) # same as above print(af.sqrt(af.sum(af.pow(X, 2), 1))) # norm of every row vector @@ -167,9 +165,10 @@ # [gettingstarted9-snippet] -import arrayfire as af import math +import arrayfire as af + # Generate a 5x5 array of uniformly distributed random numbers A = af.randu((5, 5)) @@ -188,7 +187,6 @@ # [gettingstarted9-endsnippet] - # [gettingstarted10-snippet] import arrayfire as af @@ -233,8 +231,8 @@ import arrayfire as af # Define host arrays -h_A = ([1, 1, 0, 0, 4, 0, 0, 2, 0]) -h_B = ([1, 0, 1, 0, 1, 0, 1, 1, 1]) +h_A = [1, 1, 0, 0, 4, 0, 0, 2, 0] +h_B = [1, 0, 1, 0, 1, 0, 1, 1, 1] # Create ArrayFire arrays A and B from host arrays A = af.Array(h_A, dims=(3, 3)) @@ -256,11 +254,11 @@ # [gettingstarted12-endsnippet] - # [gettingstarted13-snippet] import arrayfire as af + def main(): # Generate random values a = af.randu(10000, dtype=af.Dtype.f32) @@ -271,5 +269,3 @@ def main(): # [gettingstarted13-endsnippet] - - diff --git a/docs/indexing.py b/docs/indexing.py index e5d2364..f83a1d7 100644 --- a/docs/indexing.py +++ b/docs/indexing.py @@ -1,50 +1,48 @@ # [indexing1-snippet] -import arrayfire as af +import arrayfire as af -data = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] +data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] A = af.Array(data) -A = af.moddims(A,(4,4)) +A = af.moddims(A, (4, 4)) # [indexing1-endsnippet] # [indexing2-snippet] -A[0,0] #Returns an array pointing to the first element +A[0, 0] # Returns an array pointing to the first element -A[2,3] #WARN: avoid doing this. Demo only +A[2, 3] # WARN: avoid doing this. Demo only # [indexing2-endsnippet] # [indexing3-snippet] -ref0 = A[2,-1] # 14 second row last column -ref1 = A[2,-2] # 10 Second row, second to last(third) column +ref0 = A[2, -1] # 14 second row last column +ref1 = A[2, -2] # 10 Second row, second to last(third) column # [indexing3-endsnippet] # [indexing4-snippet] -#Returns an array pointing to the third column -A[:,2] +# Returns an array pointing to the third column +A[:, 2] # [indexing4-endsnippet] - # [indexing5-snippet] -#Returns an array pointing to the second row +# Returns an array pointing to the second row A[1, :] # [indexing5-endsnippet] - # [indexing6-snippet] - #Returns an array pointing to the first two columns +# Returns an array pointing to the first two columns A[:, 0:2] # [indexing6-endsnippet] @@ -72,12 +70,12 @@ # [indexing9-snippet] -inputA = af.constant(3,(10,10)) -inputB = af.constant(2,(10,10)) -data = af.constant(1,(10,10)) +inputA = af.constant(3, (10, 10)) +inputB = af.constant(2, (10, 10)) +data = af.constant(1, (10, 10)) -#Points to the second column of data. Does not allocate memory -ref = data[:,1] +# Points to the second column of data. Does not allocate memory +ref = data[:, 1] # This call does NOT update data. Memory allocated in matmul ref = af.matmul(inputA, inputB) @@ -114,16 +112,15 @@ # [indexing13-snippet] -A = af.Array[1,2,3,4,5,6,7,8,9] -A = af.moddims(A,(3,3)) +A = af.Array[1, 2, 3, 4, 5, 6, 7, 8, 9] +A = af.moddims(A, (3, 3)) # 1.0000 4.0000 7.0000 # 2.0000 5.0000 8.0000 # 3.0000 6.0000 9.0000 -print(A[0,0]) # first element +print(A[0, 0]) # first element # 1.0000 -print(A[0,1]) # first row, second column +print(A[0, 1]) # first row, second column # 4.0000 # [indexing13-endsnippet] - diff --git a/docs/introductiontovectorization.py b/docs/introductiontovectorization.py index 4551d1b..e7bdfd4 100644 --- a/docs/introductiontovectorization.py +++ b/docs/introductiontovectorization.py @@ -1,4 +1,3 @@ - # [vectorization1-snippet] import arrayfire as af @@ -19,11 +18,11 @@ import arrayfire as af -#[0, 9] +# [0, 9] a = af.range(10) # [1, 10] -a = a+ 1 +a = a + 1 # [vectorization2-endsnippet] @@ -32,9 +31,7 @@ import arrayfire as af # Define the filter coefficients as a list -g_coef = [1, 2, 1, - 2, 4, 2, - 1, 2, 1] +g_coef = [1, 2, 1, 2, 4, 2, 1, 2, 1] # Convert the coefficients list to an ArrayFire array and scale it filter = (1.0 / 16.0) * af.Array(3, 3, g_coef) @@ -130,7 +127,7 @@ import arrayfire as af # Create the filter and weight vectors -filter = af.randu((1, 5)) # Shape: 1x5 +filter = af.randu((1, 5)) # Shape: 1x5 weights = af.randu((5, 5)) # Shape: 5x5 # Transpose the filter to align dimensions for broadcasting @@ -141,7 +138,7 @@ # Print the filtered weights array print("Filtered weights:") -print(filtered_weights) # Incorrect +print(filtered_weights) # Incorrect # [vectorization10-endsnippet] # [vectorization11-snippet] @@ -149,13 +146,13 @@ import arrayfire as af # Create the filter and weight vectors -filter = af.randu((1, 5)) # Shape: 1x5 -batched_filter = af.tile(filter, (1, 1, 5)) # batch on the third dimension +filter = af.randu((1, 5)) # Shape: 1x5 +batched_filter = af.tile(filter, (1, 1, 5)) # batch on the third dimension weights = af.randu((5, 5)) # Shape: 5x5 # Leverage matmul batching -filtered_weights = af.matmul(batched_filter, weights) # shape 1x5x5 -filtered_weights = af.moddims(filtered_weights, (5, 5)) # reshape to 2d 5x5 +filtered_weights = af.matmul(batched_filter, weights) # shape 1x5x5 +filtered_weights = af.moddims(filtered_weights, (5, 5)) # reshape to 2d 5x5 # Print the filtered weights array print("Filtered weights:") diff --git a/docs/overview.py b/docs/overview.py index 18ee70a..1a4d093 100644 --- a/docs/overview.py +++ b/docs/overview.py @@ -1,5 +1,6 @@ import arrayfire as af + # [pi-example-simple-snippet] # Monte Carlo estimation of pi def calc_pi_device(samples) -> float: @@ -12,5 +13,6 @@ def calc_pi_device(samples) -> float: within_unit_circle = (x * x + y * y) < 1 # Intuitive function names return 4 * af.count(within_unit_circle) / samples -# [pi-example-simple-endsnippet] + +# [pi-example-simple-endsnippet] diff --git a/docs/release_notes.md b/docs/release_notes.md new file mode 100644 index 0000000..cc17462 --- /dev/null +++ b/docs/release_notes.md @@ -0,0 +1,21 @@ +Release Notes {#releasenotes} +============== + +v0.1.0 +====== +Welcome to the ArrayFire Python Bindings! These are the currently supported features: + +- Support for all backends (cpu, opencl, oneapi, cuda) +- Computer Vision +- Functions to Create and Modify Arrays +- Functions to Work with Internal Array Layout +- Image Processing with Features +- Input and Output Functions +- Interface Functions +- Linear Algebra +- Machine Learning +- Mathematical Functions +- Signal Processing +- Statistics +- Unified API Functions +- Vector Algorithms \ No newline at end of file diff --git a/tests/test_documentation/test_documentation.py b/tests/test_documentation/test_documentation.py index ddbb0de..94651b0 100644 --- a/tests/test_documentation/test_documentation.py +++ b/tests/test_documentation/test_documentation.py @@ -1,8 +1,8 @@ -import pytest -import arrayfire as af import math +import pytest +import arrayfire as af def test_array_shapes(): @@ -19,9 +19,10 @@ def test_array_shapes(): assert array_2d.shape == (10, 100) # Check shape of 2D array assert array_3d.shape == (10, 10, 10) # Check shape of 3D array - # [pi-example-simple-snippet] # Monte Carlo estimation of pi + + def calc_pi_device(samples) -> float: # Simple, array based API # Generate uniformly distributed random numers @@ -34,6 +35,7 @@ def calc_pi_device(samples) -> float: return 4 * af.count(within_unit_circle) / samples # [pi-example-simple-endsnippet] + def test_calc_pi_device(): samples = 100000 x = af.randu(samples) @@ -52,82 +54,87 @@ def test_calc_pi_device(): # Generate an array of size three filled with zeros. # If no data type is specified, ArrayFire defaults to f32. # The constant function generates the data on the device. -zeroes = af.constant(0,(3,)) +zeroes = af.constant(0, (3,)) # Generate a 1x4 array of uniformly distributed [0,1] random numbers # The randu function generates the data on the device. -rand1 = af.randu((1,4)) +rand1 = af.randu((1, 4)) # Generate a 2x2 array (or matrix, if you prefer) of random numbers # sampled from a normal distribution. # The randn function generates data on the device. -rand2 = af.randu((2,2)) +rand2 = af.randu((2, 2)) # Generate a 3x3 identity matrix. The data is generated on the device. -iden = af.identity((3,3)) +iden = af.identity((3, 3)) # Lastly, create a 2x1 array (column vector) of uniformly distributed # 32-bit complex numbers (c32 data type): -randcplx = af.randu((2,1)) +randcplx = af.randu((2, 1)) # [gettingstarted2-endsnippet] import pytest + import arrayfire as af + def test_arrayfire_operations(): # Generate an array of size three filled with zeros zeroes = af.constant(0, (3,)) assert zeroes.shape == (3,) # Check shape - + # Generate a 1x4 array of uniformly distributed [0,1] random numbers rand1 = af.randu((1, 4)) assert rand1.shape == (1, 4) # Check shape - + # Generate a 2x2 array of random numbers sampled from a normal distribution rand2 = af.randn((2, 2)) assert rand2.shape == (2, 2) # Check shape - + # Generate a 3x3 identity matrix - iden = af.identity((3,3)) + iden = af.identity((3, 3)) assert iden.shape == (3, 3) # Check shape - + # Generate a 2x1 array (column vector) of uniformly distributed 32-bit complex numbers randcplx = af.randu((2, 1)) - assert randcplx.shape == (2, ) # Check shape + assert randcplx.shape == (2,) # Check shape + # [gettingstarted3-snippet] import arrayfire as af + # Create a six-element array on the host -hA = ([0, 1, 2, 3, 4, 5]) +hA = [0, 1, 2, 3, 4, 5] # Which can be copied into an ArrayFire Array using the pointer copy # constructor. Here we copy the data into a 2x3 matrix: -A = af.moddims(af.Array(hA),(2,3)) +A = af.moddims(af.Array(hA), (2, 3)) # ArrayFire provides a convenince function for printing array # objects in case you wish to see how the data is stored: print(A) -#todo how to create complex numbers +# todo how to create complex numbers # [gettingstarted3-endsnippet] def test_arrayfire_conversion(): # Create a six-element array on the host - hA = ([0, 1, 2, 3, 4, 5]) - + hA = [0, 1, 2, 3, 4, 5] + # Copy data from host array to an ArrayFire array and reshape to 2x3 matrix - A = af.moddims(af.Array(hA),(2,3)) - + A = af.moddims(af.Array(hA), (2, 3)) + # Assert that the shape of A is (2, 3) assert A.shape == (2, 3) - + # Assert that the elements in A match hA for i in range(2): for j in range(3): assert A[i, j] == hA[i * 3 + j] + # [gettingstarted11-snippet] import arrayfire as af @@ -144,37 +151,41 @@ def test_arrayfire_conversion(): import pytest + import arrayfire as af + def test_arrayfire_scalar_value(): # Create an array consisting of 3 random numbers a = af.randu(3) - + # Get the scalar value of the array val = a.scalar() - + # Assert that the scalar value is a float assert isinstance(val, float) - + # Assert that the scalar value is between 0 and 1 (inclusive) assert 0 <= val <= 1 + def test_vectorization(): # [vectorization2-snippet] import arrayfire as af - #[0, 9] + # [0, 9] a = af.range(10) # [1, 10] - a = a+ 1 + a = a + 1 # [vectorization2-endsnippet] # Assertion: Verify the elements of the array 'a' expected_result = af.Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) assert a == expected_result + def test_apply_filter(): # [vectorization9-snippet] @@ -194,17 +205,15 @@ def test_apply_filter(): print(filtered_weights) # [vectorization9-endsnippet] assert filtered_weights.shape == (5, 5) - - -def test_filtered_weights(): +def test_filtered_weights(): # [vectorization10-snippet] import arrayfire as af # Create the filter and weight vectors - filter = af.randu((1, 5)) # Shape: 1x5 + filter = af.randu((1, 5)) # Shape: 1x5 weights = af.randu((5, 5)) # Shape: 5x5 # Transpose the filter to align dimensions for broadcasting @@ -214,7 +223,7 @@ def test_filtered_weights(): filtered_weights = filter_transposed * weights expected_shape = (5, 5) # Expected shape of filtered_weights - + # Assertions assert filtered_weights.shape == expected_shape @@ -223,6 +232,7 @@ def test_filtered_weights(): print(filtered_weights) # [vectorization10-endsnippet] + def test_flatten_array(): # [manipulation1-snippet] @@ -243,7 +253,7 @@ def test_flatten_array(): def test_flip_array(): - + # [manipulation2-snippet] import arrayfire as af @@ -270,6 +280,7 @@ def test_flip_array(): assert af.min(flip_a) >= 0 assert af.max(flip_a) < 1 + def test_join_array(): # [manipulation3-snippet] @@ -292,7 +303,6 @@ def test_join_array(): assert a_join.shape == (10,) - def test_moddims_operations(): # [manipulation4-snippet] @@ -302,11 +312,11 @@ def test_moddims_operations(): print(a) - moddims_a = af.moddims(a,(2,4)) + moddims_a = af.moddims(a, (2, 4)) print(moddims_a) - moddims_b = af.moddims(a,(len(a),)) + moddims_b = af.moddims(a, (len(a),)) print(moddims_b) # [manipulation4-endsnippet] @@ -316,28 +326,27 @@ def test_moddims_operations(): assert a == moddims_b - def test_arrayfire_shift(): # [manipulation6-snippet] import arrayfire as af - a = af.randu((3,5)) + a = af.randu((3, 5)) print(a) - a_shift = af.shift(a,(0,2)) + a_shift = af.shift(a, (0, 2)) print(a_shift) - a_shift1 = af.shift(a,(-1,2)) + a_shift1 = af.shift(a, (-1, 2)) print(a_shift1) # [manipulation6-endsnippet] # Check if arrays are equal by comparing element-wise - assert a != a_shift + assert a != a_shift assert a != a_shift1 - assert a_shift.shape == (3,5) - assert a_shift1.shape == (3,5) + assert a_shift.shape == (3, 5) + assert a_shift1.shape == (3, 5) def transpose_arrayifre(): @@ -345,20 +354,19 @@ def transpose_arrayifre(): import arrayfire as af - a = af.randu((3,3)) - print(a) #[3 3 1 1] + a = af.randu((3, 3)) + print(a) # [3 3 1 1] - ''' 0.3949 0.8465 0.3709 + """ 0.3949 0.8465 0.3709 0.3561 0.9399 0.2751 - 0.6097 0.6802 0.2720''' - + 0.6097 0.6802 0.2720""" a_transpose = af.transpose(a) - print(a_transpose) #[3 3 1 1] + print(a_transpose) # [3 3 1 1] - ''' 0.3949 0.3561 0.6097 + """ 0.3949 0.3561 0.6097 0.8465 0.9399 0.6802 - 0.3709 0.2751 0.2720''' + 0.3709 0.2751 0.2720""" # [manipulation8-endsnippet] # Convert arrays to Python lists for comparison a_list = a.to_array().tolist() @@ -371,32 +379,21 @@ def transpose_arrayifre(): assert a_transpose_list == expected_a_transpose - - def test_moddims(): # [indexing1-snippet] - import arrayfire as af + import arrayfire as af - data = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] + data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] A = af.Array(data) - A = af.moddims(A,(4,4)) - -# [indexing1-endsnippet] - expected_result = [ - [0, 1, 2, 3], - [4, 5, 6, 7], - [8, 9, 10, 11], - [12, 13, 14, 15] - ] - + A = af.moddims(A, (4, 4)) + + # [indexing1-endsnippet] + expected_result = [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]] + dims = A.shape A_list = [[A[i, j] for j in range(dims[1])] for i in range(dims[0])] # Check if the reshaped array matches the expected result assert A_list == expected_result - - - -