diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 000000000..c3ca58a39 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,29 @@ +name: Run Tests + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Set up Python 3.10 + uses: actions/setup-python@v1 + with: + 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 + + - name: Install AF + run: apt install arrayfire + + - name: Test array_api + run: python -m pytest arrayfire/array_api diff --git a/.gitignore b/.gitignore index ba7466050..047939aae 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ htmlcov/ nosetests.xml coverage.xml *,cover +.pytest_cache # Translations *.mo @@ -55,3 +56,9 @@ docs/_build/ # PyBuilder target/ + +# mypy +.mypy_cache + +# Virtual environment +venv diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..154589c05 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "assets"] + path = assets + url = https://github.com/arrayfire/assets.git diff --git a/CHANGELOG.md b/CHANGELOG.md index e446bc598..77ddf3024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,186 @@ +### v3.6.20181017 +- Feature parity with ArrayFire v3.6. Refer to the [release notes](https://github.com/arrayfire/arrayfire/blob/master/docs/pages/release_notes.md) for more information regarding upstream library improvements in v3.6. + - `anisotropic_diffusion()`: Anisotropic diffusion filter. + - `topk()`: Returns top-K elements given an array. +- Bug fixes: + - Fixed `sift()` and `gloh()`, which were improperly calling the library. +- Enhancements: + - Added `len()` method, which returns `array.elements()`. +- Documentation: + - Documented statistics API. + - Corrected `sign()` documentation. + - Modified `helloworld` example to match C++ lib. + +### v3.5.20170721 +- Bug fixes when using v3.5 of arrayfire libs + graphics + +### v3.5.20170721 +- Bug fixes for canny edge detection + +### v3.5.20170718 +- Feature parity with ArrayFire 3.5. + - `canny`: Canny Edge detector + - `Array.scalar`: Return the first element of the array + - `dot`: Now support option to return scalar + - `print_mem_info`: Prints memory being used / locked by arrayfire memory manager. + - `Array.allocated`: Returs the amount of memory allocated for the given buffer. + - `set_fft_plan_cache_size`: Sets the size of the fft plan cache. + +- Bug Fixes: + - `sort_by_key` had key and value flipped in documentation. + +- Improvements and bugfixes from upstream include: + - CUDA backend uses nvrtc instead of nvvm + - Performance improvements to arrayfire.reorder + - Faster unified backend + - You can find more information at arrayfire's [release notes](https://github.com/arrayfire/arrayfire/blob/v3.5.0/docs/pages/release_notes.md) + +### v3.4.20170222 +- Bugfix: Fixes typo in `approx1`. +- Bugfix: Fixes typo in `hamming_matcher` and `nearest_neighbour`. +- Bugfix: Added necessary copy and lock mechanisms in interop.py. +- Example / Benchmark: New conjugate gradient benchmark. +- Feature: Added support to create arrayfire arrays from numba. +- Behavior change: af.print() only prints full arrays for smaller sizes. + +### v3.4.20161126 +- Fixing memory leak in array creation. +- Supporting 16 bit integer types in interop. + +### v3.4.20160925 +- Feature parity with ArrayFire 3.4 libs + + - [Sparse matrix support](http://arrayfire.org/arrayfire-python/arrayfire.sparse.html#module-arrayfire.sparse) + - `create_sparse` + - `create_sparse_from_dense` + - `create_sparse_from_host` + - `convert_sparse_to_dense` + - `convert_sparse` + - `sparse_get_info` + - `sparse_get_nnz` + - `sparse_get_values` + - `sparse_get_row_idx` + - `sparse_get_col_idx` + - `sparse_get_storage` + + - [Random Engine support](http://arrayfire.org/arrayfire-python/arrayfire.random.html#module-arrayfire.random) + - Three new random engines, `RANDOM_ENGINE.PHILOX`, `RANDOM_ENGINE.THREEFRY`, and `RANDOM_ENGINE.MERSENNE`. + - `randu` and `randn` now accept an additional engine parameter. + - `set_default_random_engine_type` + - `get_default_random_engine` + + - New functions + - [`scan`](http://arrayfire.org/arrayfire-python/arrayfire.algorithm.html?arrayfire.algorithm.scan#arrayfire.algorithm.scan) + - [`scan_by_key`](http://arrayfire.org/arrayfire-python/arrayfire.algorithm.html?arrayfire.algorithm.scan#arrayfire.algorithm.scan_by_key) + - [`clamp`](http://arrayfire.org/arrayfire-python/arrayfire.arith.html?arrayfire.arith.clamp#arrayfire.arith.clamp) + - [`medfilt1`](http://arrayfire.org/arrayfire-python/arrayfire.signal.html#arrayfire.signal.medfilt1) + - [`medfilt2`](http://arrayfire.org/arrayfire-python/arrayfire.signal.html#arrayfire.signal.medfilt2) + - [`moments`](http://arrayfire.org/arrayfire-python/arrayfire.image.html#arrayfire.image.moments) + - [`get_size_of`](http://arrayfire.org/arrayfire-python/arrayfire.library.html#arrayfire.library.get_size_of) + - [`get_manual_eval_flag`](http://arrayfire.org/arrayfire-python/arrayfire.device.html#arrayfire.device.get_manual_eval_flag) + - [`set_manual_eval_flag`](http://arrayfire.org/arrayfire-python/arrayfire.device.html#arrayfire.device.set_manual_eval_flag) + + - Behavior changes + - [`eval`](http://arrayfire.org/arrayfire-python/arrayfire.device.html#arrayfire.device.eval) now supports fusing kernels. + + - Graphics updates + - [`plot`](http://arrayfire.org/arrayfire-python/arrayfire.graphics.html#arrayfire.graphics.Window.plot) updated to take new parameters. + - [`plot2`](http://arrayfire.org/arrayfire-python/arrayfire.graphics.html#arrayfire.graphics.Window.plot2) added. + - [`scatter`](http://arrayfire.org/arrayfire-python/arrayfire.graphics.html#arrayfire.graphics.Window.scatter) updated to take new parameters. + - [`scatter2`](http://arrayfire.org/arrayfire-python/arrayfire.graphics.html#arrayfire.graphics.Window.scatter2) added. + - [`vector_field`](http://arrayfire.org/arrayfire-python/arrayfire.graphics.html#arrayfire.graphics.Window.vector_field) added. + - [`set_axes_limits`](http://arrayfire.org/arrayfire-python/arrayfire.graphics.html#arrayfire.graphics.Window.set_axes_limits) added. + +- Bug fixes + + - ArrayFire now has higher priority when numpy for mixed operations. [1](https://github.com/arrayfire/arrayfire-python/issues/69) [2](https://github.com/arrayfire/arrayfire-python/pull/71) + - Numpy interoperability issues on Widnows. [1](https://github.com/arrayfire/arrayfire-python/issues/92) + - Switch to a working backend by default. [1](https://github.com/arrayfire/arrayfire-python/issues/90) + - Fixed incorrect behavior for Hermitian transpose and QR. [1](https://github.com/arrayfire/arrayfire-python/issues/91) + - `array[0:0]` now returns empty arrays. [1](https://github.com/arrayfire/arrayfire-python/issues/26) + +- Further Improvements from upstream can be read in the [arrayfire release notes](https://github.com/arrayfire/arrayfire/blob/master/docs/pages/release_notes.md). + +### v3.3.20160624 +- Adding 16 bit integer support +- Adding support for sphinx documentation + +### v3.3.20160516 +- Bugfix: Increase arrayfire's priority over numpy for mixed operations + +- Added new library functions + - `get_backend` returns backend name + +### v3.3.20160510 +- Bugfix to `af.histogram` + +- Added missing functions / methods + - `gaussian_kernel` + +- Added new array properties + - `Array.T` now returns transpose + - `Array.H` now returns hermitian transpose + - `Array.shape` now allows easier access individual dimensions + +### v3.3.20160427 +- Fixes to numpy interop on Windows +- Fixes issues with occasional double free +- Fixes to graphics examples + +### v3.3.20160328 +- Fixes to make arrayfire-python to work on 32 bit systems + +### v3.3.20160320 +- Feature parity with Arrayfire 3.3 libs + - Functions to interact with arryafire's internal data structures. + - `Array.offset` + - `Array.strides` + - `Array.is_owner` + - `Array.is_linear` + - `Array.raw_ptr` + - Array constructor now takes `offset` and `strides` as optional parameters. + - New visualization functions: `scatter` and `scatter3` + - OpenCL backend specific functions: + - `get_device_type` + - `get_platform` + - `add_device_context` + - `delete_device_context` + - `set_device_context` + - Functions to allocate and free memory on host and device + - `alloc_host` and `free_host` + - `alloc_pinned` and `free_pinned` + - `alloc_device` and `free_device` + - Function to query which device and backend an array was created on + - `get_device_id` + - `get_backend_id` + - Miscellaneous functions + - `is_lapack_available` + - `is_image_io_available` + +- Interopability + - Transfer PyCUDA GPUArrays using `af.pycuda_to_af_array` + - Transfer PyOpenCL Arrays using `af.pyopencl_to_af_array` + - New helper function `af.to_array` added to convert a different `array` to arrayfire Array. + - This function can be used in place of `af.xyz_to_af_array` functions mentioned above. + +- Deprecated functions list + - `lock_device_ptr` is deprecated. Use `lock_array` instead. + - `unlock_device_ptr` is deprecated. Use `unlock_array` instead. + +- Bug Fixes: + - [Boolean indexing giving faulty results](https://github.com/arrayfire/arrayfire-python/issues/68) for multi dimensional arrays. + - [Enum types comparision failures](https://github.com/arrayfire/arrayfire-python/issues/65) in Python 2.x + - [Support loading SO versioned libraries](https://github.com/arrayfire/arrayfire-python/issues/64) in Linux and OSX. + - Fixed typo that prevented changing backend + - Fixed image processing functions that accepted floating point scalar paramters. + - Affected functions include: `translate`, `scale`, `skew`, `histogram`, `bilateral`, `mean_shift`. +### v3.2.20151224 +- Bug fixes: + - A default `AF_PATH` is set if none is found as an environment variable. + +- Examples: + - Heston model example uses a smaller data set to help run on low end GPUs. + ### v3.2.20151214 - Bug fixes: - `get_version()` now returns ints instead of `c_int` diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..4c1808108 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.11.0) +project(arrayfire-python) +find_package(PythonExtensions REQUIRED) +include(FetchContent) + +set(CMAKE_MODULE_PATH_OLD ${CMAKE_MODULE_PATH}) +set(CMAKE_MODULE_PATH "") +set(NO_SONAME) + +FetchContent_Declare( + arrayfire + GIT_REPOSITORY https://github.com/arrayfire/arrayfire.git + GIT_TAG v3.8 +) +FetchContent_MakeAvailable(arrayfire) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH_OLD}) + +set(ignoreWarning "${SKBUILD}") diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..88db0a145 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +recursive-include docs * +recursive-include examples * +include CHANGELOG.md LICENSE README.md diff --git a/README.md b/README.md index f3576d179..737399adb 100644 --- a/README.md +++ b/README.md @@ -2,119 +2,76 @@ [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 provides Python bindings for the ArrayFire library. +## Documentation + +Documentation for this project can be found [over here](http://arrayfire.org/arrayfire-python/). + ## Example +```python +# Monte Carlo estimation of pi +def calc_pi_device(samples): + # Simple, array based API + # Generate uniformly distributed random numers + x = af.randu(samples) + y = af.randu(samples) + # Supports Just In Time Compilation + # The following line generates a single kernel + within_unit_circle = (x * x + y * y) < 1 + # Intuitive function names + return 4 * af.count(within_unit_circle) / samples ``` -import arrayfire as af -# Display backend information -af.info() -# Generate a uniform random array with a size of 5 elements -a = af.randu(5, 1) +Choosing a particular backend can be done using `af.set_backend(name)` where name is either "_cuda_", "_opencl_", or "_cpu_". The default device is chosen in the same order of preference. -# Print a and its minimum value -af.display(a) - -# Print min and max values of a -print("Minimum, Maximum: ", af.min(a), af.max(a)) -``` +## Getting started +ArrayFire can be installed from a variety of sources. [Pre-built wheels](https://repo.arrayfire.com/python/wheels/3.8.0/) are available for a number of systems and toolkits. These will include a distribution of the ArrayFire libraries. Currently, only the python wrapper is available on PyPI. Wrapper-only installations will require a separate installation of the ArrayFire C/C++ libraries. +You can get the ArrayFire C/C++ library from the following sources: -## Sample outputs +- [Download and install binaries](https://arrayfire.com/download) +- [Build and install from source](https://github.com/arrayfire/arrayfire) -On an AMD GPU: +**Install the last stable version of python wrapper:** ``` -Using opencl backend -ArrayFire v3.0.1 (OpenCL, 64-bit Linux, build 17db1c9) -[0] AMD : Spectre --1- AMD : AMD A10-7850K Radeon R7, 12 Compute Cores 4C+8G - -[5 1 1 1] -0.4107 -0.8224 -0.9518 -0.1794 -0.4198 - -Minimum, Maximum: 0.17936542630195618 0.9517996311187744 +pip install arrayfire ``` -On an NVIDIA GPU: - +**Install a pre-built wheel for a specific CUDA toolkit version:** ``` -Using cuda backend -ArrayFire v3.0.0 (CUDA, 64-bit Linux, build 86426db) -Platform: CUDA Toolkit 7, Driver: 346.46 -[0] Tesla K40c, 12288 MB, CUDA Compute 3.5 --1- GeForce GTX 750, 1024 MB, CUDA Compute 5.0 - -Generate a random matrix a: -[5 1 1 1] -0.7402 -0.9210 -0.0390 -0.9690 -0.9251 - -Minimum, Maximum: 0.039020489901304245 0.9689629077911377 +pip install arrayfire==3.8.0+cu112 -f https://repo.arrayfire.com/python/wheels/3.8.0/ +# Replace the +cu112 local version with the desired toolkit ``` -Fallback to CPU when CUDA and OpenCL are not availabe: +**Install the development source distribution:** ``` -Using cpu backend -ArrayFire v3.0.0 (CPU, 64-bit Linux, build 86426db) - -Generate a random matrix a: -[5 1 1 1] -0.0000 -0.1315 -0.7556 -0.4587 -0.5328 - -Minimum, Maximum: 7.825903594493866e-06 0.7556053400039673 +pip install git+git://github.com/arrayfire/arrayfire-python.git@master ``` -Choosing a particular backend can be done using `af.backend.set( backend_name )` where backend_name can be one of: "_cuda_", "_opencl_", or "_cpu_". The default device is chosen in the same order of preference. - -## Requirements - -Currently, this project is tested only on Linux and OSX. You also need to have the ArrayFire C/C++ library installed on your machine. You can get it from the following sources. - -- [Download and install binaries](https://arrayfire.com/download) -- [Build and install from source](https://github.com/arrayfire/arrayfire) - -Please check the following links for dependencies. - -- [Linux dependencies](http://www.arrayfire.com/docs/using_on_linux.htm) -- [OSX dependencies](http://www.arrayfire.com/docs/using_on_osx.htm) - -## Getting started - -**Install the last stable version:** +**Installing offline:** ``` -pip install arrayfire +cd path/to/arrayfire-python +python setup.py install ``` +Rather than installing and building ArrayFire elsewhere in the system, you can also build directly through python by first setting the `AF_BUILD_LOCAL_LIBS=1` environment variable. Additional setup will be required to build ArrayFire, including satisfying dependencies and further CMake configuration. Details on how to pass additional arguments to the build systems can be found in the [scikit-build documentation.](https://scikit-build.readthedocs.io/en/latest/) -**Install the development version:** +**Post Installation:** -``` -pip install git+git://github.com/arrayfire/arrayfire.git@devel -``` +If you are not using one of the pre-built wheels, you may need to ensure arrayfire-python can find the installed arrayfire libraries. Please follow [these instructions](https://github.com/arrayfire/arrayfire-python/wiki) to ensure that arrayfire-python can find the arrayfire libraries. -**Installing offline** +To run arrayfire tests, you can run the following command from command line. ``` -cd path/to/arrayfire-python -python setup.py install +python -m arrayfire.tests ``` -**Post Installation** +## Communication -Please follow [these instructions](https://github.com/arrayfire/arrayfire-python/wiki) to ensure the arrayfire-python can find the arrayfire libraries. +* [Slack Chat](https://join.slack.com/t/arrayfire-org/shared_invite/MjI4MjIzMDMzMTczLTE1MDI5ODg4NzYtN2QwNGE3ODA5OQ) +* [Google Groups](https://groups.google.com/forum/#!forum/arrayfire-users) ## Acknowledgements diff --git a/tests/simple_tests.py b/__af_version__.py old mode 100755 new mode 100644 similarity index 56% rename from tests/simple_tests.py rename to __af_version__.py index 67fb8c982..16f06c5ae --- a/tests/simple_tests.py +++ b/__af_version__.py @@ -9,17 +9,6 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## -import simple -import sys - -if __name__ == "__main__": - verbose = False - - if len(sys.argv) > 1: - verbose = int(sys.argv[1]) != False - - test_list = None - if len(sys.argv) > 2: - test_list = sys.argv[2:] - - simple.tests.run(test_list, verbose) +version = "3.8" +release = "20210303" +full_version = version + "." + release diff --git a/arrayfire/__init__.py b/arrayfire/__init__.py index 79959bb8c..5cb009951 100644 --- a/arrayfire/__init__.py +++ b/arrayfire/__init__.py @@ -8,18 +8,22 @@ ######################################################## """ -A high performance scientific computing library for CUDA, OpenCL and CPU devices. +ArrayFire is a high performance scientific computing library with an easy to use API. -The functionality provided by ArrayFire spans the following domains: - 1. Vector Algorithms - 2. Image Processing - 3. Signal Processing - 4. Computer Vision - 5. Linear Algebra - 6. Statistics + >>> # Monte Carlo estimation of pi + >>> def calc_pi_device(samples): + # Simple, array based API + # Generate uniformly distributed random numers + x = af.randu(samples) + y = af.randu(samples) + # Supports Just In Time Compilation + # The following line generates a single kernel + within_unit_circle = (x * x + y * y) < 1 + # Intuitive function names + return 4 * af.count(within_unit_circle) / samples -Programs written using ArrayFire are portable across CUDA, OpenCL and CPU devices +Programs written using ArrayFire are portable across CUDA, OpenCL and CPU devices. The default backend is chosen in the following order of preference based on the available libraries: @@ -29,12 +33,26 @@ The backend can be chosen at the beginning of the program by using the following function - >>> af.backend.set(name) + >>> af.set_backend(name) -where name is one of 'cuda', 'opencl' or 'cpu' +where name is one of 'cuda', 'opencl' or 'cpu'. + +The functionality provided by ArrayFire spans the following domains: + + 1. Vector Algorithms + 2. Image Processing + 3. Signal Processing + 4. Computer Vision + 5. Linear Algebra + 6. Statistics """ +try: + import pycuda.autoinit +except ImportError: + pass + from .library import * from .array import * from .data import * @@ -54,6 +72,9 @@ from .index import * from .interop import * from .timer import * +from .random import * +from .sparse import * +from .ml import * # do not export default modules as part of arrayfire del ct diff --git a/arrayfire/algorithm.py b/arrayfire/algorithm.py index 823663766..6f06cee14 100644 --- a/arrayfire/algorithm.py +++ b/arrayfire/algorithm.py @@ -8,7 +8,7 @@ ######################################################## """ -Vector algorithms for ArrayFire +Vector algorithms (sum, min, sort, etc). """ from .library import * @@ -16,14 +16,14 @@ def _parallel_dim(a, dim, c_func): out = Array() - safe_call(c_func(ct.pointer(out.arr), a.arr, ct.c_int(dim))) + safe_call(c_func(c_pointer(out.arr), a.arr, c_int_t(dim))) return out def _reduce_all(a, c_func): - real = ct.c_double(0) - imag = ct.c_double(0) + real = c_double_t(0) + imag = c_double_t(0) - safe_call(c_func(ct.pointer(real), ct.pointer(imag), a.arr)) + safe_call(c_func(c_pointer(real), c_pointer(imag), a.arr)) real = real.value imag = imag.value @@ -31,19 +31,44 @@ def _reduce_all(a, c_func): def _nan_parallel_dim(a, dim, c_func, nan_val): out = Array() - safe_call(c_func(ct.pointer(out.arr), a.arr, ct.c_int(dim), ct.c_double(nan_val))) + safe_call(c_func(c_pointer(out.arr), a.arr, c_int_t(dim), c_double_t(nan_val))) return out def _nan_reduce_all(a, c_func, nan_val): - real = ct.c_double(0) - imag = ct.c_double(0) + real = c_double_t(0) + imag = c_double_t(0) - safe_call(c_func(ct.pointer(real), ct.pointer(imag), a.arr, ct.c_double(nan_val))) + safe_call(c_func(c_pointer(real), c_pointer(imag), a.arr, c_double_t(nan_val))) real = real.value imag = imag.value return real if imag == 0 else real + imag * 1j +def _FNSD(dim, dims): + if dim >= 0: + return int(dim) + + fnsd = 0 + for i, d in enumerate(dims): + if d > 1: + fnsd = i + break + return int(fnsd) + +def _rbk_dim(keys, vals, dim, c_func): + keys_out = Array() + vals_out = Array() + rdim = _FNSD(dim, vals.dims()) + safe_call(c_func(c_pointer(keys_out.arr), c_pointer(vals_out.arr), keys.arr, vals.arr, c_int_t(rdim))) + return keys_out, vals_out + +def _nan_rbk_dim(a, dim, c_func, nan_val): + keys_out = Array() + vals_out = Array() + rdim = _FNSD(dim, vals.dims()) + safe_call(c_func(c_pointer(keys_out.arr), c_pointer(vals_out.arr), keys.arr, vals.arr, c_int_t(rdim), c_double_t(nan_val))) + return keys_out, vals_out + def sum(a, dim=None, nan_val=None): """ Calculate the sum of all the elements along a specified dimension. @@ -74,6 +99,34 @@ def sum(a, dim=None, nan_val=None): else: return _reduce_all(a, backend.get().af_sum_all) + +def sumByKey(keys, vals, dim=-1, nan_val=None): + """ + Calculate the sum of elements along a specified dimension according to a key. + + Parameters + ---------- + keys : af.Array + One dimensional arrayfire array with reduction keys. + vals : af.Array + Multi dimensional arrayfire array that will be reduced. + dim: optional: int. default: -1 + Dimension along which the sum will occur. + nan_val: optional: scalar. default: None + The value that replaces NaN in the array + + Returns + ------- + keys: af.Array or scalar number + The reduced keys of all elements in `vals` along dimension `dim`. + values: af.Array or scalar number + The sum of all elements in `vals` along dimension `dim` according to keys + """ + if (nan_val is not None): + return _nan_rbk_dim(keys, vals, dim, backend.get().af_sum_by_key_nan, nan_val) + else: + return _rbk_dim(keys, vals, dim, backend.get().af_sum_by_key) + def product(a, dim=None, nan_val=None): """ Calculate the product of all the elements along a specified dimension. @@ -104,6 +157,33 @@ def product(a, dim=None, nan_val=None): else: return _reduce_all(a, backend.get().af_product_all) +def productByKey(keys, vals, dim=-1, nan_val=None): + """ + Calculate the product of elements along a specified dimension according to a key. + + Parameters + ---------- + keys : af.Array + One dimensional arrayfire array with reduction keys. + vals : af.Array + Multi dimensional arrayfire array that will be reduced. + dim: optional: int. default: -1 + Dimension along which the product will occur. + nan_val: optional: scalar. default: None + The value that replaces NaN in the array + + Returns + ------- + keys: af.Array or scalar number + The reduced keys of all elements in `vals` along dimension `dim`. + values: af.Array or scalar number + The product of all elements in `vals` along dimension `dim` according to keys + """ + if (nan_val is not None): + return _nan_rbk_dim(keys, vals, dim, backend.get().af_product_by_key_nan, nan_val) + else: + return _rbk_dim(keys, vals, dim, backend.get().af_product_by_key) + def min(a, dim=None): """ Find the minimum value of all the elements along a specified dimension. @@ -126,6 +206,28 @@ def min(a, dim=None): else: return _reduce_all(a, backend.get().af_min_all) +def minByKey(keys, vals, dim=-1): + """ + Calculate the min of elements along a specified dimension according to a key. + + Parameters + ---------- + keys : af.Array + One dimensional arrayfire array with reduction keys. + vals : af.Array + Multi dimensional arrayfire array that will be reduced. + dim: optional: int. default: -1 + Dimension along which the min will occur. + + Returns + ------- + keys: af.Array or scalar number + The reduced keys of all elements in `vals` along dimension `dim`. + values: af.Array or scalar number + The min of all elements in `vals` along dimension `dim` according to keys + """ + return _rbk_dim(keys, vals, dim, backend.get().af_min_by_key) + def max(a, dim=None): """ Find the maximum value of all the elements along a specified dimension. @@ -148,6 +250,56 @@ def max(a, dim=None): else: return _reduce_all(a, backend.get().af_max_all) +def maxRagged(vals, lens, dim): + """ + Find the maximum value of a subset of elements along a specified dimension. + + The size of the subset of elements along the given dimension are decided based on the lengths + provided in the `lens` array. + + Parameters + ---------- + vals : af.Array + Multi dimensional arrayfire array. + lens : af.Array + Multi dimensional arrayfire array containing number of elements to reduce along given `dim` + dim: optional: int. default: None + Dimension along which the maximum value is required. + + Returns + ------- + (values, indices): A tuple of af.Array(s) + `values` af.Array will have the maximum values along given dimension for + subsets determined by lengths provided in `lens` + `idx` contains the locations of the maximum values as per the lengths provided in `lens` + """ + out_vals = Array() + out_idx = Array() + safe_call(backend().get().af_max_ragged(c_pointer(out_vals.arr), c_pointer(out_idx.arr), c_pointer(vals.arr), c_pointer(lens.arr), c_int_t(dim))) + return out_vals, out_idx + +def maxByKey(keys, vals, dim=-1): + """ + Calculate the max of elements along a specified dimension according to a key. + + Parameters + ---------- + keys : af.Array + One dimensional arrayfire array with reduction keys. + vals : af.Array + Multi dimensional arrayfire array that will be reduced. + dim: optional: int. default: -1 + Dimension along which the max will occur. + + Returns + ------- + keys: af.Array or scalar number + The reduced keys of all elements in `vals` along dimension `dim`. + values: af.Array or scalar number + The max of all elements in `vals` along dimension `dim` according to keys. + """ + return _rbk_dim(keys, vals, dim, backend.get().af_max_by_key) + def all_true(a, dim=None): """ Check if all the elements along a specified dimension are true. @@ -170,6 +322,28 @@ def all_true(a, dim=None): else: return _reduce_all(a, backend.get().af_all_true_all) +def allTrueByKey(keys, vals, dim=-1): + """ + Calculate if all elements are true along a specified dimension according to a key. + + Parameters + ---------- + keys : af.Array + One dimensional arrayfire array with reduction keys. + vals : af.Array + Multi dimensional arrayfire array that will be reduced. + dim: optional: int. default: -1 + Dimension along which the all true check will occur. + + Returns + ------- + keys: af.Array or scalar number + The reduced keys of all true check in `vals` along dimension `dim`. + values: af.Array or scalar number + Booleans denoting if all elements are true in `vals` along dimension `dim` according to keys + """ + return _rbk_dim(keys, vals, dim, backend.get().af_all_true_by_key) + def any_true(a, dim=None): """ Check if any the elements along a specified dimension are true. @@ -192,6 +366,28 @@ def any_true(a, dim=None): else: return _reduce_all(a, backend.get().af_any_true_all) +def anyTrueByKey(keys, vals, dim=-1): + """ + Calculate if any elements are true along a specified dimension according to a key. + + Parameters + ---------- + keys : af.Array + One dimensional arrayfire array with reduction keys. + vals : af.Array + Multi dimensional arrayfire array that will be reduced. + dim: optional: int. default: -1 + Dimension along which the any true check will occur. + + Returns + ------- + keys: af.Array or scalar number + The reduced keys of any true check in `vals` along dimension `dim`. + values: af.Array or scalar number + Booleans denoting if any elements are true in `vals` along dimension `dim` according to keys. + """ + return _rbk_dim(keys, vals, dim, backend.get().af_any_true_by_key) + def count(a, dim=None): """ Count the number of non zero elements in an array along a specified dimension. @@ -214,6 +410,28 @@ def count(a, dim=None): else: return _reduce_all(a, backend.get().af_count_all) +def countByKey(keys, vals, dim=-1): + """ + Counts non-zero elements along a specified dimension according to a key. + + Parameters + ---------- + keys : af.Array + One dimensional arrayfire array with reduction keys. + vals : af.Array + Multi dimensional arrayfire array that will be reduced. + dim: optional: int. default: -1 + Dimension along which to count elements. + + Returns + ------- + keys: af.Array or scalar number + The reduced keys of count in `vals` along dimension `dim`. + values: af.Array or scalar number + Count of non-zero elements in `vals` along dimension `dim` according to keys. + """ + return _rbk_dim(keys, vals, dim, backend.get().af_count_by_key) + def imin(a, dim=None): """ Find the value and location of the minimum value along a specified dimension @@ -235,13 +453,13 @@ def imin(a, dim=None): if dim is not None: out = Array() idx = Array() - safe_call(backend.get().af_imin(ct.pointer(out.arr), ct.pointer(idx.arr), a.arr, ct.c_int(dim))) + safe_call(backend.get().af_imin(c_pointer(out.arr), c_pointer(idx.arr), a.arr, c_int_t(dim))) return out,idx else: - real = ct.c_double(0) - imag = ct.c_double(0) - idx = ct.c_uint(0) - safe_call(backend.get().af_imin_all(ct.pointer(real), ct.pointer(imag), ct.pointer(idx), a.arr)) + real = c_double_t(0) + imag = c_double_t(0) + idx = c_uint_t(0) + safe_call(backend.get().af_imin_all(c_pointer(real), c_pointer(imag), c_pointer(idx), a.arr)) real = real.value imag = imag.value val = real if imag == 0 else real + imag * 1j @@ -268,13 +486,13 @@ def imax(a, dim=None): if dim is not None: out = Array() idx = Array() - safe_call(backend.get().af_imax(ct.pointer(out.arr), ct.pointer(idx.arr), a.arr, ct.c_int(dim))) + safe_call(backend.get().af_imax(c_pointer(out.arr), c_pointer(idx.arr), a.arr, c_int_t(dim))) return out,idx else: - real = ct.c_double(0) - imag = ct.c_double(0) - idx = ct.c_uint(0) - safe_call(backend.get().af_imax_all(ct.pointer(real), ct.pointer(imag), ct.pointer(idx), a.arr)) + real = c_double_t(0) + imag = c_double_t(0) + idx = c_uint_t(0) + safe_call(backend.get().af_imax_all(c_pointer(real), c_pointer(imag), c_pointer(idx), a.arr)) real = real.value imag = imag.value val = real if imag == 0 else real + imag * 1j @@ -283,7 +501,7 @@ def imax(a, dim=None): def accum(a, dim=0): """ - Cumulative sum of an array along a specified dimension. + Cumulative sum of an array along a specified dimension Parameters ---------- @@ -299,6 +517,71 @@ def accum(a, dim=0): """ return _parallel_dim(a, dim, backend.get().af_accum) +def scan(a, dim=0, op=BINARYOP.ADD, inclusive_scan=True): + """ + Generalized scan of an array. + + Parameters + ---------- + a : af.Array + Multi dimensional arrayfire array. + + dim : optional: int. default: 0 + Dimension along which the scan is performed. + + op : optional: af.BINARYOP. default: af.BINARYOP.ADD. + Binary option the scan algorithm uses. Can be one of: + - af.BINARYOP.ADD + - af.BINARYOP.MUL + - af.BINARYOP.MIN + - af.BINARYOP.MAX + + inclusive_scan: optional: bool. default: True + Specifies if the scan is inclusive + + Returns + --------- + out : af.Array + - will contain scan of input. + """ + out = Array() + safe_call(backend.get().af_scan(c_pointer(out.arr), a.arr, dim, op.value, inclusive_scan)) + return out + +def scan_by_key(key, a, dim=0, op=BINARYOP.ADD, inclusive_scan=True): + """ + Generalized scan by key of an array. + + Parameters + ---------- + key : af.Array + key array. + + a : af.Array + Multi dimensional arrayfire array. + + dim : optional: int. default: 0 + Dimension along which the scan is performed. + + op : optional: af.BINARYOP. default: af.BINARYOP.ADD. + Binary option the scan algorithm uses. Can be one of: + - af.BINARYOP.ADD + - af.BINARYOP.MUL + - af.BINARYOP.MIN + - af.BINARYOP.MAX + + inclusive_scan: optional: bool. default: True + Specifies if the scan is inclusive + + Returns + --------- + out : af.Array + - will contain scan of input. + """ + out = Array() + safe_call(backend.get().af_scan_by_key(c_pointer(out.arr), key.arr, a.arr, dim, op.value, inclusive_scan)) + return out + def where(a): """ Find the indices of non zero elements @@ -314,7 +597,7 @@ def where(a): Linear indices for non zero elements. """ out = Array() - safe_call(backend.get().af_where(ct.pointer(out.arr), a.arr)) + safe_call(backend.get().af_where(c_pointer(out.arr), a.arr)) return out def diff1(a, dim=0): @@ -376,7 +659,7 @@ def sort(a, dim=0, is_ascending=True): Currently `dim` is only supported for 0. """ out = Array() - safe_call(backend.get().af_sort(ct.pointer(out.arr), a.arr, ct.c_uint(dim), ct.c_bool(is_ascending))) + safe_call(backend.get().af_sort(c_pointer(out.arr), a.arr, c_uint_t(dim), c_bool_t(is_ascending))) return out def sort_index(a, dim=0, is_ascending=True): @@ -404,20 +687,20 @@ def sort_index(a, dim=0, is_ascending=True): """ out = Array() idx = Array() - safe_call(backend.get().af_sort_index(ct.pointer(out.arr), ct.pointer(idx.arr), a.arr, - ct.c_uint(dim), ct.c_bool(is_ascending))) + safe_call(backend.get().af_sort_index(c_pointer(out.arr), c_pointer(idx.arr), a.arr, + c_uint_t(dim), c_bool_t(is_ascending))) return out,idx -def sort_by_key(iv, ik, dim=0, is_ascending=True): +def sort_by_key(ik, iv, dim=0, is_ascending=True): """ Sort an array based on specified keys Parameters ---------- - iv : af.Array - An Array containing the values ik : af.Array An Array containing the keys + iv : af.Array + An Array containing the values dim: optional: int. default: 0 Dimension along which sort is to be performed. is_ascending: optional: bool. default: True @@ -425,9 +708,9 @@ def sort_by_key(iv, ik, dim=0, is_ascending=True): Returns ------- - (ov, ok): tuple of af.Array - `ov` contains the values from `iv` after sorting them based on `ik` + (ok, ov): tuple of af.Array `ok` contains the values from `ik` in sorted order + `ov` contains the values from `iv` after sorting them based on `ik` Note ------- @@ -435,8 +718,8 @@ def sort_by_key(iv, ik, dim=0, is_ascending=True): """ ov = Array() ok = Array() - safe_call(backend.get().af_sort_by_key(ct.pointer(ov.arr), ct.pointer(ok.arr), - iv.arr, ik.arr, ct.c_uint(dim), ct.c_bool(is_ascending))) + safe_call(backend.get().af_sort_by_key(c_pointer(ok.arr), c_pointer(ov.arr), + ik.arr, iv.arr, c_uint_t(dim), c_bool_t(is_ascending))) return ov,ok def set_unique(a, is_sorted=False): @@ -456,7 +739,7 @@ def set_unique(a, is_sorted=False): an array containing the unique values from `a` """ out = Array() - safe_call(backend.get().af_set_unique(ct.pointer(out.arr), a.arr, ct.c_bool(is_sorted))) + safe_call(backend.get().af_set_unique(c_pointer(out.arr), a.arr, c_bool_t(is_sorted))) return out def set_union(a, b, is_unique=False): @@ -478,7 +761,7 @@ def set_union(a, b, is_unique=False): an array values after performing the union of `a` and `b`. """ out = Array() - safe_call(backend.get().af_set_union(ct.pointer(out.arr), a.arr, b.arr, ct.c_bool(is_unique))) + safe_call(backend.get().af_set_union(c_pointer(out.arr), a.arr, b.arr, c_bool_t(is_unique))) return out def set_intersect(a, b, is_unique=False): @@ -500,5 +783,5 @@ def set_intersect(a, b, is_unique=False): an array values after performing the intersect of `a` and `b`. """ out = Array() - safe_call(backend.get().af_set_intersect(ct.pointer(out.arr), a.arr, b.arr, ct.c_bool(is_unique))) + safe_call(backend.get().af_set_intersect(c_pointer(out.arr), a.arr, b.arr, c_bool_t(is_unique))) return out diff --git a/arrayfire/arith.py b/arrayfire/arith.py index 9dc8c666f..e4dc2fdfd 100644 --- a/arrayfire/arith.py +++ b/arrayfire/arith.py @@ -8,7 +8,7 @@ ######################################################## """ -Math functions for ArrayFire +Math functions (sin, sqrt, exp, etc). """ from .library import * @@ -26,27 +26,27 @@ def _arith_binary_func(lhs, rhs, c_func): raise TypeError("Atleast one input needs to be of type arrayfire.array") elif (is_left_array and is_right_array): - safe_call(c_func(ct.pointer(out.arr), lhs.arr, rhs.arr, _bcast_var.get())) + safe_call(c_func(c_pointer(out.arr), lhs.arr, rhs.arr, _bcast_var.get())) elif (_is_number(rhs)): ldims = dim4_to_tuple(lhs.dims()) rty = implicit_dtype(rhs, lhs.type()) other = Array() other.arr = constant_array(rhs, ldims[0], ldims[1], ldims[2], ldims[3], rty) - safe_call(c_func(ct.pointer(out.arr), lhs.arr, other.arr, _bcast_var.get())) + safe_call(c_func(c_pointer(out.arr), lhs.arr, other.arr, _bcast_var.get())) else: rdims = dim4_to_tuple(rhs.dims()) lty = implicit_dtype(lhs, rhs.type()) other = Array() other.arr = constant_array(lhs, rdims[0], rdims[1], rdims[2], rdims[3], lty) - safe_call(c_func(ct.pointer(out.arr), other.arr, rhs.arr, _bcast_var.get())) + safe_call(c_func(c_pointer(out.arr), other.arr, rhs.arr, _bcast_var.get())) return out def _arith_unary_func(a, c_func): out = Array() - safe_call(c_func(ct.pointer(out.arr), a.arr)) + safe_call(c_func(c_pointer(out.arr), a.arr)) return out def cast(a, dtype): @@ -75,7 +75,7 @@ def cast(a, dtype): array containing the values from `a` after converting to `dtype`. """ out=Array() - safe_call(backend.get().af_cast(ct.pointer(out.arr), a.arr, dtype.value)) + safe_call(backend.get().af_cast(c_pointer(out.arr), a.arr, dtype.value)) return out def minof(lhs, rhs): @@ -126,6 +126,64 @@ def maxof(lhs, rhs): """ return _arith_binary_func(lhs, rhs, backend.get().af_maxof) +def clamp(val, low, high): + """ + Clamp the input value between low and high + + + Parameters + ---------- + val : af.Array + Multi dimensional arrayfire array to be clamped. + + low : af.Array or scalar + Multi dimensional arrayfire array or a scalar number denoting the lower value(s). + + high : af.Array or scalar + Multi dimensional arrayfire array or a scalar number denoting the higher value(s). + """ + out = Array() + + is_low_array = isinstance(low, Array) + is_high_array = isinstance(high, Array) + + vdims = dim4_to_tuple(val.dims()) + vty = val.type() + + if not is_low_array: + low_arr = constant_array(low, vdims[0], vdims[1], vdims[2], vdims[3], vty) + else: + low_arr = low.arr + + if not is_high_array: + high_arr = constant_array(high, vdims[0], vdims[1], vdims[2], vdims[3], vty) + else: + high_arr = high.arr + + safe_call(backend.get().af_clamp(c_pointer(out.arr), val.arr, low_arr, high_arr, _bcast_var.get())) + + return out + +def mod(lhs, rhs): + """ + Find the modulus. + Parameters + ---------- + lhs : af.Array or scalar + Multi dimensional arrayfire array or a scalar number. + rhs : af.Array or scalar + Multi dimensional arrayfire array or a scalar number. + Returns + -------- + out : af.Array + Contains the moduli after dividing each value of lhs` with those in `rhs`. + Note + ------- + - Atleast one of `lhs` and `rhs` needs to be af.Array. + - If `lhs` and `rhs` are both af.Array, they must be of same size. + """ + return _arith_binary_func(lhs, rhs, backend.get().af_mod) + def rem(lhs, rhs): """ Find the remainder. @@ -194,7 +252,7 @@ def sign(a): Returns -------- out : af.Array - array containing -1 for negative values, 1 otherwise. + array containing 1 for negative values, 0 otherwise. """ return _arith_unary_func(a, backend.get().af_sign) @@ -900,6 +958,26 @@ def sqrt(a): """ return _arith_unary_func(a, backend.get().af_sqrt) +def rsqrt(a): + """ + Reciprocal or inverse square root of each element in the array. + + Parameters + ---------- + a : af.Array + Multi dimensional arrayfire array. + + Returns + -------- + out : af.Array + array containing the inverse square root of each value from `a`. + + Note + ------- + `a` must not be complex. + """ + return _arith_unary_func(a, backend.get().af_rsqrt) + def cbrt(a): """ Cube root of each element in the array. diff --git a/arrayfire/array.py b/arrayfire/array.py index 13a5fc8fa..1b71db2c7 100644 --- a/arrayfire/array.py +++ b/arrayfire/array.py @@ -8,10 +8,11 @@ ######################################################## """ -arrayfire.Array class and helper functions. +Array class and helper functions. """ import inspect +import os from .library import * from .util import * from .util import _is_number @@ -20,22 +21,105 @@ from .index import * from .index import _Index4 +_is_running_in_py_charm = "PYCHARM_HOSTED" in os.environ + +_display_dims_limit = None + +def set_display_dims_limit(*dims): + """ + Sets the dimension limit after which array's data won't get + presented to the result of str(arr). + + Default is None, which means there is no limit. + + Parameters + ---------- + *dims : dimension limit args + + Example + ------- + set_display_dims_limit(10, 10, 10, 10) + + """ + 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 + presented to the result of str(arr). + + Default is None, which means there is no limit. + + Returns + ----------- + - tuple of the current limit + - None is there is no limit + + Example + ------- + get_display_dims_limit() + # None + set_display_dims_limit(10, 10, 10, 10) + get_display_dims_limit() + # (10, 10, 10, 10) + + """ + return _display_dims_limit + +def _in_display_dims_limit(dims): + if _is_running_in_py_charm: + return False + if _display_dims_limit is not None: + limit_len = len(_display_dims_limit) + dim_len = len(dims) + if dim_len > limit_len: + return False + for i in range(dim_len): + if dims[i] > _display_dims_limit[i]: + return False + return True + def _create_array(buf, numdims, idims, dtype, is_device): - out_arr = ct.c_void_p(0) + out_arr = c_void_ptr_t(0) c_dims = dim4(idims[0], idims[1], idims[2], idims[3]) if (not is_device): - safe_call(backend.get().af_create_array(ct.pointer(out_arr), ct.c_void_p(buf), - numdims, ct.pointer(c_dims), dtype.value)) + safe_call(backend.get().af_create_array(c_pointer(out_arr), c_void_ptr_t(buf), + numdims, c_pointer(c_dims), dtype.value)) + else: + safe_call(backend.get().af_device_array(c_pointer(out_arr), c_void_ptr_t(buf), + 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]) + if offset is None: + offset = 0 + offset = c_dim_t(offset) + if strides is None: + strides = (1, idims[0], idims[0]*idims[1], idims[0]*idims[1]*idims[2]) + while len(strides) < 4: + strides = strides + (strides[-1],) + strides = dim4(strides[0], strides[1], strides[2], strides[3]) + if is_device: + location = Source.device else: - safe_call(backend.get().af_device_array(ct.pointer(out_arr), ct.c_void_p(buf), - numdims, ct.pointer(c_dims), dtype.value)) + location = Source.host + safe_call(backend.get().af_create_strided_array(c_pointer(out_arr), c_void_ptr_t(buf), + offset, numdims, c_pointer(c_dims), + c_pointer(strides), dtype.value, + location.value)) return out_arr def _create_empty_array(numdims, idims, dtype): - out_arr = ct.c_void_p(0) + 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(ct.pointer(out_arr), - numdims, ct.pointer(c_dims), dtype.value)) + 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): @@ -43,35 +127,35 @@ 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. """ - if not isinstance(dtype, ct.c_int): + if not isinstance(dtype, c_int_t): if isinstance(dtype, int): - dtype = ct.c_int(dtype) + dtype = c_int_t(dtype) elif isinstance(dtype, Dtype): - dtype = ct.c_int(dtype.value) + dtype = c_int_t(dtype.value) else: raise TypeError("Invalid dtype") - out = ct.c_void_p(0) + out = c_void_ptr_t(0) dims = dim4(d0, d1, d2, d3) if isinstance(val, complex): - c_real = ct.c_double(val.real) - c_imag = ct.c_double(val.imag) + c_real = c_double_t(val.real) + c_imag = c_double_t(val.imag) if (dtype.value != Dtype.c32.value and dtype.value != Dtype.c64.value): dtype = Dtype.c32.value - safe_call(backend.get().af_constant_complex(ct.pointer(out), c_real, c_imag, - 4, ct.pointer(dims), dtype)) + safe_call(backend.get().af_constant_complex(c_pointer(out), c_real, c_imag, + 4, c_pointer(dims), dtype)) elif dtype.value == Dtype.s64.value: - c_val = ct.c_longlong(val.real) - safe_call(backend.get().af_constant_long(ct.pointer(out), c_val, 4, ct.pointer(dims))) + c_val = c_longlong_t(val.real) + safe_call(backend.get().af_constant_long(c_pointer(out), c_val, 4, c_pointer(dims))) elif dtype.value == Dtype.u64.value: - c_val = ct.c_ulonglong(val.real) - safe_call(backend.get().af_constant_ulong(ct.pointer(out), c_val, 4, ct.pointer(dims))) + c_val = c_ulonglong_t(val.real) + safe_call(backend.get().af_constant_ulong(c_pointer(out), c_val, 4, c_pointer(dims))) else: - c_val = ct.c_double(val) - safe_call(backend.get().af_constant(ct.pointer(out), c_val, 4, ct.pointer(dims), dtype)) + c_val = c_double_t(val) + safe_call(backend.get().af_constant(c_pointer(out), c_val, 4, c_pointer(dims), dtype)) return out @@ -88,7 +172,7 @@ def _binary_func(lhs, rhs, c_func): elif not isinstance(rhs, Array): raise TypeError("Invalid parameter to binary function") - safe_call(c_func(ct.pointer(out.arr), lhs.arr, other.arr, _bcast_var.get())) + safe_call(c_func(c_pointer(out.arr), lhs.arr, other.arr, _bcast_var.get())) return out @@ -104,7 +188,7 @@ def _binary_funcr(lhs, rhs, c_func): elif not isinstance(lhs, Array): raise TypeError("Invalid parameter to binary function") - c_func(ct.pointer(out.arr), other.arr, rhs.arr, _bcast_var.get()) + c_func(c_pointer(out.arr), other.arr, rhs.arr, _bcast_var.get()) return out @@ -139,28 +223,24 @@ def _slice_to_length(key, dim): def _get_info(dims, buf_len): elements = 1 - numdims = len(dims) - idims = [1]*4 - - for i in range(numdims): - elements *= dims[i] - idims[i] = dims[i] - - if (elements == 0): - if (buf_len != 0): - idims = [buf_len, 1, 1, 1] - numdims = 1 - else: - raise RuntimeError("Invalid size") + numdims = 0 + if dims: + numdims = len(dims) + idims = [1]*4 + for i in range(numdims): + elements *= dims[i] + idims[i] = dims[i] + elif (buf_len != 0): + idims = [buf_len, 1, 1, 1] + numdims = 1 + else: + raise RuntimeError("Invalid size") return numdims, idims def _get_indices(key): - - S = Index(slice(None)) - inds = _Index4(S, S, S, S) - + inds = _Index4() if isinstance(key, tuple): n_idx = len(key) for n in range(n_idx): @@ -216,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. @@ -236,7 +315,7 @@ def transpose(a, conj=False): """ out = Array() - safe_call(backend.get().af_transpose(ct.pointer(out.arr), a.arr, conj)) + safe_call(backend.get().af_transpose(c_pointer(out.arr), a.arr, conj)) return out def transpose_inplace(a, conj=False): @@ -279,23 +358,29 @@ class Array(BaseArray): - 'd' for double - 'b' for bool - 'B' for unsigned char + - 'h' for signed 16 bit integer + - 'H' for unsigned 16 bit integer - 'i' for signed 32 bit integer - 'I' for unsigned 32 bit integer - 'l' for signed 64 bit integer - 'L' for unsigned 64 bit integer - 'F' for 32 bit complex number - 'D' for 64 bit complex number + - if arrayfire.Dtype, must be one of the following: - Dtype.f32 for float - Dtype.f64 for double - Dtype.b8 for bool - Dtype.u8 for unsigned char + - Dtype.s16 for signed 16 bit integer + - Dtype.u16 for unsigned 16 bit integer - Dtype.s32 for signed 32 bit integer - Dtype.u32 for unsigned 32 bit integer - Dtype.s64 for signed 64 bit integer - Dtype.u64 for unsigned 64 bit integer - Dtype.c32 for 32 bit complex number - Dtype.c64 for 64 bit complex number + - if None, Dtype.f32 is assumed Attributes @@ -352,7 +437,13 @@ class Array(BaseArray): """ - def __init__(self, src=None, dims=(0,), dtype=None, is_device=False): + # 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 + + def __init__(self, src=None, dims=None, dtype=None, is_device=False, offset=None, strides=None): super(Array, self).__init__() @@ -372,7 +463,7 @@ def __init__(self, src=None, dims=(0,), dtype=None, is_device=False): if src is not None: if (isinstance(src, Array)): - safe_call(backend.get().af_retain_array(ct.pointer(self.arr), src.arr)) + safe_call(backend.get().af_retain_array(c_pointer(self.arr), src.arr)) return host = __import__("array") @@ -386,8 +477,8 @@ def __init__(self, src=None, dims=(0,), dtype=None, is_device=False): buf,buf_len = tmp.buffer_info() _type_char = tmp.typecode numdims, idims = _get_info(dims, buf_len) - elif isinstance(src, int) or isinstance(src, ct.c_void_p): - buf = src if not isinstance(src, ct.c_void_p) else src.value + elif isinstance(src, int) or isinstance(src, c_void_ptr_t): + buf = src if not isinstance(src, c_void_ptr_t) else src.value numdims, idims = _get_info(dims, buf_len) @@ -409,18 +500,24 @@ def __init__(self, src=None, dims=(0,), dtype=None, is_device=False): if (type_char is not None and type_char != _type_char): raise TypeError("Can not create array of requested type from input data type") - - self.arr = _create_array(buf, numdims, idims, to_dtype[_type_char], is_device) + if(offset is None and strides is None): + self.arr = _create_array(buf, numdims, idims, to_dtype[_type_char], is_device) + else: + self.arr = _create_strided_array(buf, numdims, idims, + to_dtype[_type_char], + is_device, offset, strides) else: if type_char is None: type_char = 'f' - numdims = len(dims) + numdims = len(dims) if dims else 0 + idims = [1] * 4 for n in range(numdims): idims[n] = dims[n] + self.arr = _create_empty_array(numdims, idims, to_dtype[type_char]) def as_type(self, ty): @@ -443,7 +540,7 @@ def copy(self): An identical copy of self. """ out = Array() - safe_call(backend.get().af_copy_array(ct.pointer(out.arr), self.arr)) + safe_call(backend.get().af_copy_array(c_pointer(out.arr), self.arr)) return out def __del__(self): @@ -452,13 +549,35 @@ def __del__(self): """ if self.arr.value: backend.get().af_release_array(self.arr) + self.arr.value = 0 def device_ptr(self): + """ + Return the device pointer exclusively held by the array. + + Returns + -------- + ptr : int + Contains location of the device pointer + + Note + ---- + - This can be used to integrate with custom C code and / or PyCUDA or PyOpenCL. + - Implies `af.device.lock_array()`. The device pointer of `a` is not freed by memory manager until `unlock_device_ptr()` is called. + - No other arrays will share the same device pointer. + - A copy of the memory is done if multiple arrays share the same memory or the array is not the owner of the memory. + - In case of a copy the return value points to the newly allocated memory which is now exclusively owned by the array. + """ + ptr = c_void_ptr_t(0) + backend.get().af_get_device_ptr(c_pointer(ptr), self.arr) + return ptr.value + + def raw_ptr(self): """ Return the device pointer held by the array. Returns - ------ + -------- ptr : int Contains location of the device pointer @@ -466,25 +585,70 @@ def device_ptr(self): ---- - This can be used to integrate with custom C code and / or PyCUDA or PyOpenCL. - No mem copy is peformed, this function returns the raw device pointer. + - This pointer may be shared with other arrays. Use this function with caution. + - In particular the JIT compiler will not be aware of the shared arrays. + - This results in JITed operations not being immediately visible through the other array. """ - ptr = ct.c_void_p(0) - backend.get().af_get_device_ptr(ct.pointer(ptr), self.arr) + ptr = c_void_ptr_t(0) + backend.get().af_get_raw_ptr(c_pointer(ptr), self.arr) return ptr.value + def offset(self): + """ + Return the offset, of the first element relative to the raw pointer. + + Returns + -------- + offset : int + The offset in number of elements + """ + offset = c_dim_t(0) + safe_call(backend.get().af_get_offset(c_pointer(offset), self.arr)) + return offset.value + + def strides(self): + """ + Return the distance in bytes between consecutive elements for each dimension. + + Returns + -------- + strides : tuple + The strides for each dimension + """ + s0 = c_dim_t(0) + s1 = c_dim_t(0) + 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) + return strides[:self.numdims()] + def elements(self): """ Return the number of elements in the array. """ - num = ct.c_ulonglong(0) - safe_call(backend.get().af_get_elements(ct.pointer(num), self.arr)) + num = c_dim_t(0) + safe_call(backend.get().af_get_elements(c_pointer(num), self.arr)) + return num.value + + def __len__(self): + return(self.elements()) + + def allocated(self): + """ + Returns the number of bytes allocated by the memory manager for the array. + """ + num = c_size_t(0) + safe_call(backend.get().af_get_allocated_bytes(c_pointer(num), self.arr)) return num.value def dtype(self): """ Return the data type as a arrayfire.Dtype enum value. """ - dty = ct.c_int(Dtype.f32.value) - safe_call(backend.get().af_get_type(ct.pointer(dty), self.arr)) + dty = c_int_t(Dtype.f32.value) + safe_call(backend.get().af_get_type(c_pointer(dty), self.arr)) return to_dtype[to_typecode[dty.value]] def type(self): @@ -493,57 +657,78 @@ def type(self): """ return self.dtype().value + @property + def T(self): + """ + Return the transpose of the array + """ + return transpose(self, False) + + @property + def H(self): + """ + Return the hermitian transpose of the array + """ + return transpose(self, True) + def dims(self): """ Return the shape of the array as a tuple. """ - d0 = ct.c_longlong(0) - d1 = ct.c_longlong(0) - d2 = ct.c_longlong(0) - d3 = ct.c_longlong(0) - safe_call(backend.get().af_get_dims(ct.pointer(d0), ct.pointer(d1), - ct.pointer(d2), ct.pointer(d3), self.arr)) + 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(c_pointer(d0), c_pointer(d1), + c_pointer(d2), c_pointer(d3), self.arr)) dims = (d0.value,d1.value,d2.value,d3.value) return dims[:self.numdims()] + @property + def shape(self): + """ + The shape of the array + """ + return self.dims() + def numdims(self): """ Return the number of dimensions of the array. """ - nd = ct.c_uint(0) - safe_call(backend.get().af_get_numdims(ct.pointer(nd), self.arr)) + nd = c_uint_t(0) + safe_call(backend.get().af_get_numdims(c_pointer(nd), self.arr)) return nd.value def is_empty(self): """ Check if the array is empty i.e. it has no elements. """ - res = ct.c_bool(False) - safe_call(backend.get().af_is_empty(ct.pointer(res), self.arr)) + res = c_bool_t(False) + safe_call(backend.get().af_is_empty(c_pointer(res), self.arr)) return res.value def is_scalar(self): """ Check if the array is scalar i.e. it has only one element. """ - res = ct.c_bool(False) - safe_call(backend.get().af_is_scalar(ct.pointer(res), self.arr)) + res = c_bool_t(False) + safe_call(backend.get().af_is_scalar(c_pointer(res), self.arr)) return res.value def is_row(self): """ Check if the array is a row i.e. it has a shape of (1, cols). """ - res = ct.c_bool(False) - safe_call(backend.get().af_is_row(ct.pointer(res), self.arr)) + res = c_bool_t(False) + safe_call(backend.get().af_is_row(c_pointer(res), self.arr)) return res.value def is_column(self): """ Check if the array is a column i.e. it has a shape of (rows, 1). """ - res = ct.c_bool(False) - safe_call(backend.get().af_is_column(ct.pointer(res), self.arr)) + res = c_bool_t(False) + safe_call(backend.get().af_is_column(c_pointer(res), self.arr)) return res.value def is_vector(self): @@ -554,72 +739,104 @@ def is_vector(self): - (1, 1, vols) - (1, 1, 1, batch) """ - res = ct.c_bool(False) - safe_call(backend.get().af_is_vector(ct.pointer(res), self.arr)) + res = c_bool_t(False) + safe_call(backend.get().af_is_vector(c_pointer(res), self.arr)) + return res.value + + def is_sparse(self): + """ + Check if the array is a sparse matrix. + """ + res = c_bool_t(False) + safe_call(backend.get().af_is_sparse(c_pointer(res), self.arr)) return res.value def is_complex(self): """ Check if the array is of complex type. """ - res = ct.c_bool(False) - safe_call(backend.get().af_is_complex(ct.pointer(res), self.arr)) + res = c_bool_t(False) + safe_call(backend.get().af_is_complex(c_pointer(res), self.arr)) return res.value def is_real(self): """ Check if the array is not of complex type. """ - res = ct.c_bool(False) - safe_call(backend.get().af_is_real(ct.pointer(res), self.arr)) + res = c_bool_t(False) + safe_call(backend.get().af_is_real(c_pointer(res), self.arr)) return res.value def is_double(self): """ Check if the array is of double precision floating point type. """ - res = ct.c_bool(False) - safe_call(backend.get().af_is_double(ct.pointer(res), self.arr)) + res = c_bool_t(False) + safe_call(backend.get().af_is_double(c_pointer(res), self.arr)) return res.value def is_single(self): """ Check if the array is of single precision floating point type. """ - res = ct.c_bool(False) - safe_call(backend.get().af_is_single(ct.pointer(res), self.arr)) + res = c_bool_t(False) + safe_call(backend.get().af_is_single(c_pointer(res), self.arr)) + return res.value + + def is_half(self): + """ + Check if the array is of half floating point type (fp16). + """ + res = c_bool_t(False) + safe_call(backend.get().af_is_half(c_pointer(res), self.arr)) return res.value def is_real_floating(self): """ Check if the array is real and of floating point type. """ - res = ct.c_bool(False) - safe_call(backend.get().af_is_realfloating(ct.pointer(res), self.arr)) + res = c_bool_t(False) + safe_call(backend.get().af_is_realfloating(c_pointer(res), self.arr)) return res.value def is_floating(self): """ Check if the array is of floating point type. """ - res = ct.c_bool(False) - safe_call(backend.get().af_is_floating(ct.pointer(res), self.arr)) + res = c_bool_t(False) + safe_call(backend.get().af_is_floating(c_pointer(res), self.arr)) return res.value def is_integer(self): """ Check if the array is of integer type. """ - res = ct.c_bool(False) - safe_call(backend.get().af_is_integer(ct.pointer(res), self.arr)) + res = c_bool_t(False) + safe_call(backend.get().af_is_integer(c_pointer(res), self.arr)) return res.value def is_bool(self): """ Check if the array is of type b8. """ - res = ct.c_bool(False) - safe_call(backend.get().af_is_bool(ct.pointer(res), self.arr)) + res = c_bool_t(False) + safe_call(backend.get().af_is_bool(c_pointer(res), self.arr)) + return res.value + + def is_linear(self): + """ + Check if all elements of the array are contiguous. + """ + res = c_bool_t(False) + safe_call(backend.get().af_is_linear(c_pointer(res), self.arr)) + return res.value + + def is_owner(self): + """ + Check if the array owns the raw pointer or is a derived array. + """ + res = c_bool_t(False) + safe_call(backend.get().af_is_owner(c_pointer(res), self.arr)) return res.value def __add__(self, other): @@ -872,7 +1089,33 @@ def __invert__(self): """ Return ~self """ - return self == 0 + out = Array() + safe_call(backend.get().af_bitnot(c_pointer(out.arr), self.arr)) + return out + + def logical_not(self): + """ + Return ~self + """ + out = Array() + safe_call(backend.get().af_not(c_pointer(out.arr), self.arr)) + return out + + 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? + return out + + 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? + return out def __nonzero__(self): return self != 0 @@ -892,10 +1135,16 @@ def __getitem__(self, key): try: out = Array() n_dims = self.numdims() + + if (isinstance(key, Array) and key.type() == Dtype.b8.value): + n_dims = 1 + if (count(key) == 0): + return out + inds = _get_indices(key) - safe_call(backend.get().af_index_gen(ct.pointer(out.arr), - self.arr, ct.c_longlong(n_dims), inds.pointer)) + safe_call(backend.get().af_index_gen(c_pointer(out.arr), + self.arr, c_dim_t(n_dims), inds.pointer)) return out except RuntimeError as e: raise IndexError(str(e)) @@ -912,20 +1161,32 @@ def __setitem__(self, key, val): try: n_dims = self.numdims() + is_boolean_idx = isinstance(key, Array) and key.type() == Dtype.b8.value + + if (is_boolean_idx): + n_dims = 1 + num = count(key) + if (num == 0): + return + if (_is_number(val)): tdims = _get_assign_dims(key, self.dims()) - other_arr = constant_array(val, tdims[0], tdims[1], tdims[2], tdims[3], self.type()) + if (is_boolean_idx): + 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()) del_other = True else: other_arr = val.arr del_other = False - out_arr = ct.c_void_p(0) + out_arr = c_void_ptr_t(0) inds = _get_indices(key) - safe_call(backend.get().af_assign_gen(ct.pointer(out_arr), - self.arr, ct.c_longlong(n_dims), inds.pointer, - other_arr)) + safe_call(backend.get().af_assign_gen(c_pointer(out_arr), + self.arr, c_dim_t(n_dims), inds.pointer, + other_arr)) safe_call(backend.get().af_release_array(self.arr)) if del_other: safe_call(backend.get().af_release_array(other_arr)) @@ -934,12 +1195,25 @@ def __setitem__(self, key, val): except RuntimeError as e: raise IndexError(str(e)) + def _reorder(self): + """ + Returns a reordered array to help interoperate with row major formats. + """ + ndims = self.numdims() + if (ndims == 1): + return self + + rdims = tuple(reversed(range(ndims))) + tuple(range(ndims, 4)) + out = Array() + safe_call(backend.get().af_reorder(c_pointer(out.arr), self.arr, *rdims)) + return out + def to_ctype(self, row_major=False, return_shape=False): """ Return the data as a ctype C array after copying to host memory Parameters - --------- + ----------- row_major: optional: bool. default: False. Specifies if a transpose needs to occur before copying to host memory. @@ -959,10 +1233,12 @@ def to_ctype(self, row_major=False, return_shape=False): if (self.arr.value == 0): raise RuntimeError("Can not call to_ctype on empty array") - tmp = transpose(self) if row_major else self + tmp = self._reorder() if (row_major) else self + ctype_type = to_c_type[self.type()] * self.elements() res = ctype_type() - safe_call(backend.get().af_get_data_ptr(ct.pointer(res), self.arr)) + + safe_call(backend.get().af_get_data_ptr(c_pointer(res), self.arr)) if (return_shape): return res, self.dims() else: @@ -973,7 +1249,7 @@ def to_array(self, row_major=False, return_shape=False): Return the data as array.array Parameters - --------- + ----------- row_major: optional: bool. default: False. Specifies if a transpose needs to occur before copying to host memory. @@ -1008,7 +1284,7 @@ def to_list(self, row_major=False): Return the data as list Parameters - --------- + ----------- row_major: optional: bool. default: False. Specifies if a transpose needs to occur before copying to host memory. @@ -1028,20 +1304,55 @@ def to_list(self, row_major=False): ct_array, shape = self.to_ctype(row_major, True) return _ctype_to_lists(ct_array, len(shape) - 1, shape) - def __repr__(self): + def scalar(self): + """ + Return the first element of the array + """ + + if (self.arr.value == 0): + raise RuntimeError("Can not call to_ctype on empty array") + + ctype_type = to_c_type[self.type()] + res = ctype_type() + safe_call(backend.get().af_get_scalar(c_pointer(res), self.arr)) + return res.value + + def __str__(self): """ - Displays the meta data and contents of the arrayfire array. + Converts the arrayfire array to string showing its meta data and contents. Note ---- You can also use af.display(a, pres) to display the contents of the array with better precision. """ - arr_str = ct.c_char_p(0) - safe_call(backend.get().af_array_to_string(ct.pointer(arr_str), "", self.arr, 4, True)) + if not _in_display_dims_limit(self.dims()): + return self._get_metadata_str() + + return self._get_metadata_str(dims=False) + self._as_str() + + def __repr__(self): + """ + Displays the meta data of the arrayfire array. + + Note + ---- + You can use af.display(a, pres) to display the contents of the array. + """ + + return self._get_metadata_str() - return 'arrayfire.Array()\nType: %s' % \ - (to_typename[self.type()]) + to_str(arr_str) + def _get_metadata_str(self, dims=True): + return 'arrayfire.Array()\nType: {}\n{}' \ + .format(to_typename[self.type()], 'Dims: {}'.format(str(self.dims())) if dims else '') + + def _as_str(self): + arr_str = c_char_ptr_t(0) + be = backend.get() + safe_call(be.af_array_to_string(c_pointer(arr_str), "", self.arr, 4, True)) + py_str = to_str(arr_str) + safe_call(be.af_free_host(arr_str)) + return py_str def __array__(self): """ @@ -1049,9 +1360,47 @@ def __array__(self): """ import numpy as np res = np.empty(self.dims(), dtype=np.dtype(to_typecode[self.type()]), order='F') - safe_call(backend.get().af_get_data_ptr(ct.c_void_p(res.ctypes.data), self.arr)) + safe_call(backend.get().af_get_data_ptr(c_void_ptr_t(res.ctypes.data), self.arr)) return res + def to_ndarray(self, output=None): + """ + Parameters + ----------- + output: optional: numpy. default: None + + Returns + ---------- + If output is None: Constructs a numpy.array from arrayfire.Array + If output is not None: copies content of af.array into numpy array. + + Note + ------ + + - An exception is thrown when output is not None and it is not contiguous. + - When output is None, The returned array is in fortran contiguous order. + """ + if output is None: + return self.__array__() + + if (output.dtype != to_typecode[self.type()]): + raise TypeError("Output is not the same type as the array") + + if (output.size != self.elements()): + raise RuntimeError("Output size does not match that of input") + + flags = output.flags + tmp = None + if flags['F_CONTIGUOUS']: + tmp = self + elif flags['C_CONTIGUOUS']: + tmp = self._reorder() + else: + raise RuntimeError("When output is not None, it must be contiguous") + + 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. @@ -1071,11 +1420,11 @@ def display(a, precision=4): st = expr[0].find('(') + 1 en = expr[0].rfind(')') name = expr[0][st:en] - except: + except IndexError: pass safe_call(backend.get().af_print_array_gen(name.encode('utf-8'), - a.arr, ct.c_int(precision))) + a.arr, c_int_t(precision))) def save_array(key, a, filename, append=False): """ @@ -1100,8 +1449,8 @@ def save_array(key, a, filename, append=False): index : int The index of the array stored in the file. """ - index = ct.c_int(-1) - safe_call(backend.get().af_save_array(ct.pointer(index), + index = c_int_t(-1) + safe_call(backend.get().af_save_array(c_pointer(index), key.encode('utf-8'), a.arr, filename.encode('utf-8'), @@ -1132,15 +1481,15 @@ def read_array(filename, index=None, key=None): assert((index is not None) or (key is not None)) out = Array() if (index is not None): - safe_call(backend.get().af_read_array_index(ct.pointer(out.arr), + safe_call(backend.get().af_read_array_index(c_pointer(out.arr), filename.encode('utf-8'), index)) elif (key is not None): - safe_call(backend.get().af_read_array_key(ct.pointer(out.arr), + safe_call(backend.get().af_read_array_key(c_pointer(out.arr), filename.encode('utf-8'), key.encode('utf-8'))) return out -from .algorithm import sum +from .algorithm import (sum, count) from .arith import cast 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 +``` diff --git a/arrayfire/array_api/__init__.py b/arrayfire/array_api/__init__.py new file mode 100644 index 000000000..675b27ade --- /dev/null +++ b/arrayfire/array_api/__init__.py @@ -0,0 +1,9 @@ +__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..616dca797 --- /dev/null +++ b/arrayfire/array_api/array_object.py @@ -0,0 +1,1105 @@ +from __future__ import annotations + +import array as py_array +import ctypes +import enum +import warnings +from dataclasses import dataclass +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 + +from .device import PointerSource +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 + +ShapeType = Tuple[int, ...] +# 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 +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: + _no_initial_dtype = False # HACK, FIXME + warnings.warn( + "Initialisation with __init__ constructor is not a part of array-api specification" + " and about to be replaced with asarray() method.", + DeprecationWarning, stacklevel=2) + + # Initialise array object + self.arr = ctypes.c_void_p(0) + + if isinstance(dtype, str): + dtype = _str_to_dtype(dtype) # type: ignore[arg-type] + + if dtype is None: + _no_initial_dtype = True + dtype = af_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(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)) + 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()) + + 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()) + + 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) + + if not shape: + 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.") + + _type_char = dtype.typecode # type: ignore[assignment] # FIXME + + 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), _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), _cshape.original_shape, + ctypes.pointer(_cshape.c_array), dtype.c_api_value)) + 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 + + safe_call(backend.get().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)) + + # Arithmetic Operators + + def __pos__(self) -> Array: + """ + 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: + """ + 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: Union[int, float, Array], /) -> Array: + """ + 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: Union[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: Union[int, float, Array], /) -> Array: + """ + 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: Union[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: Union[int, float, Array], /) -> Array: + """ + 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: Union[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: 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. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: Union[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) + + def __floordiv__(self, other: Union[int, float, Array], /) -> Array: + # TODO + return NotImplemented + + 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. + + Parameters + ---------- + self : Array + Array instance. Should have a real-valued data type. + other: Union[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: 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 + array other. + + Parameters + ---------- + self : Array + Array instance whose elements correspond to the exponentiation base. Should have a numeric data type. + 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. + + 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_pow) + + # Array Operators + + def __matmul__(self, other: Array, /) -> Array: + # TODO get from blas - make vanilla version and not copy af.matmul as is + return NotImplemented + + # Bitwise Operators + + def __invert__(self) -> Array: + """ + 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)) + return out + + 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. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: Union[int, bool, 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: 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. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: Union[int, bool, 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: 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. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: Union[int, bool, 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: Union[int, Array], /) -> Array: + """ + 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: 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. + + 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: Union[int, Array], /) -> Array: + """ + 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: 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. + + 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) + + # Comparison Operators + + 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. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: Union[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: 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. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: Union[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: 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. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: Union[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: 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. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: Union[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: 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. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: Union[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: 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. + + Parameters + ---------- + self : Array + Array instance. Should have a numeric data type. + other: Union[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: + """ + 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) + + # 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: 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: Union[int, float, Array], /) -> Array: + """ + Return self -= other. + """ + return _process_c_function(self, other, backend.get().af_sub) + + 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: Union[int, float, Array], /) -> Array: + """ + Return self /= other. + """ + return _process_c_function(self, other, backend.get().af_div) + + def __ifloordiv__(self, other: Union[int, float, Array], /) -> Array: + # TODO + return NotImplemented + + 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: Union[int, float, 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: Union[int, bool, Array], /) -> Array: + """ + Return self &= other. + """ + return _process_c_function(self, other, backend.get().af_bitand) + + 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: Union[int, bool, Array], /) -> Array: + """ + Return self ^= other. + """ + return _process_c_function(self, other, backend.get().af_bitxor) + + 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: Union[int, Array], /) -> Array: + """ + Return self >>= other. + """ + return _process_c_function(self, other, backend.get().af_bitshiftr) + + # Methods + + def __abs__(self) -> Array: + # TODO + return NotImplemented + + def __array_namespace__(self, *, api_version: Optional[str] = None) -> Any: + # TODO + return NotImplemented + + def __bool__(self) -> bool: + # TODO consider using scalar() and is_scalar() + return NotImplemented + + def __complex__(self) -> complex: + # TODO + return NotImplemented + + 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]: + # TODO + return NotImplemented + + def __float__(self) -> float: + # TODO + return NotImplemented + + def __getitem__(self, key: Union[int, slice, Tuple[Union[int, slice, ], ...], Array], /) -> Array: + """ + Returns self[key]. + + Parameters + ---------- + self : Array + Array instance. + key : Union[int, slice, Tuple[Union[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: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], array]. + # consider using af.span to replace ellipsis during refactoring + 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 + + def __index__(self) -> int: + # TODO + return NotImplemented + + def __int__(self) -> int: + # TODO + return NotImplemented + + def __len__(self) -> int: + # NOTE not a part of the array-api spec + return self.shape[0] if self.shape else 0 + + def __setitem__( + 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 + + def __str__(self) -> str: + # NOTE not a part of the array-api spec + # 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: + # NOTE not a part of the array-api spec + # 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: Union[int, Any] = None) -> Array: + # TODO implementation and change device type from Any to Device + return NotImplemented + + # Attributes + + @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) + + @property + def device(self) -> Any: + # TODO + return NotImplemented + + @property + def mT(self) -> Array: + # TODO + return NotImplemented + + @property + 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() + 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)) + return out.value + + @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 + + @property + def shape(self) -> ShapeType: + """ + Array dimensions. + + Returns + ------- + 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) + safe_call(backend.get().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]: + """ + Return the first element of the array + """ + # NOTE not a part of the array-api spec + # TODO change the logic of this method + 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 + + def is_empty(self) -> bool: + """ + Check if the array is empty i.e. it has no elements. + """ + # NOTE not a part of the array-api spec + 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[Union[None, int, float, bool, complex]]: + # NOTE not a part of the array-api spec + 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: + # NOTE not a part of the array-api spec + 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) + # 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: Optional[ShapeType] = 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 have invalid size to process them into C shape.") + + +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 _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: + 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() + + 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)): + 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)) 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(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 + + +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], 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.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))) + 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))) + 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)) + + return out 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/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..905d1ba97 --- /dev/null +++ b/arrayfire/array_api/dtype_functions.py @@ -0,0 +1,30 @@ +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.py new file mode 100644 index 000000000..0059bf9cf --- /dev/null +++ b/arrayfire/array_api/dtypes.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +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 = [ + 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)) diff --git a/arrayfire/array_api/pytest.ini b/arrayfire/array_api/pytest.ini new file mode 100644 index 000000000..7fd828bec --- /dev/null +++ b/arrayfire/array_api/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +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/__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_object.py b/arrayfire/array_api/tests/test_array_object.py new file mode 100644 index 000000000..f30567553 --- /dev/null +++ b/arrayfire/array_api/tests/test_array_object.py @@ -0,0 +1,459 @@ +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]]) + + +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 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 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] diff --git a/arrayfire/array_api/utils.py b/arrayfire/array_api/utils.py new file mode 100644 index 000000000..779459efb --- /dev/null +++ b/arrayfire/array_api/utils.py @@ -0,0 +1,11 @@ +from .array_object import Array + +# TODO implement functions + + +def all(x: Array, /, *, axis: 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: + return NotImplemented diff --git a/arrayfire/base.py b/arrayfire/base.py index 90b220590..ca072fa2c 100644 --- a/arrayfire/base.py +++ b/arrayfire/base.py @@ -19,4 +19,4 @@ class BaseArray(object): Base array class for arrayfire. For internal use only. """ def __init__(self): - self.arr = ct.c_void_p(0) + self.arr = c_void_ptr_t(0) diff --git a/arrayfire/blas.py b/arrayfire/blas.py index c476c1c58..448261e90 100644 --- a/arrayfire/blas.py +++ b/arrayfire/blas.py @@ -8,7 +8,7 @@ ######################################################## """ -BLAS functions for arrayfire. +BLAS functions (matmul, dot, etc) """ from .library import * @@ -53,7 +53,7 @@ def matmul(lhs, rhs, lhs_opts=MATPROP.NONE, rhs_opts=MATPROP.NONE): """ out = Array() - safe_call(backend.get().af_matmul(ct.pointer(out.arr), lhs.arr, rhs.arr, + safe_call(backend.get().af_matmul(c_pointer(out.arr), lhs.arr, rhs.arr, lhs_opts.value, rhs_opts.value)) return out @@ -84,7 +84,7 @@ def matmulTN(lhs, rhs): """ out = Array() - safe_call(backend.get().af_matmul(ct.pointer(out.arr), lhs.arr, rhs.arr, + safe_call(backend.get().af_matmul(c_pointer(out.arr), lhs.arr, rhs.arr, MATPROP.TRANS.value, MATPROP.NONE.value)) return out @@ -115,7 +115,7 @@ def matmulNT(lhs, rhs): """ out = Array() - safe_call(backend.get().af_matmul(ct.pointer(out.arr), lhs.arr, rhs.arr, + safe_call(backend.get().af_matmul(c_pointer(out.arr), lhs.arr, rhs.arr, MATPROP.NONE.value, MATPROP.TRANS.value)) return out @@ -146,11 +146,11 @@ def matmulTT(lhs, rhs): """ out = Array() - safe_call(backend.get().af_matmul(ct.pointer(out.arr), lhs.arr, rhs.arr, + safe_call(backend.get().af_matmul(c_pointer(out.arr), lhs.arr, rhs.arr, MATPROP.TRANS.value, MATPROP.TRANS.value)) return out -def dot(lhs, rhs, lhs_opts=MATPROP.NONE, rhs_opts=MATPROP.NONE): +def dot(lhs, rhs, lhs_opts=MATPROP.NONE, rhs_opts=MATPROP.NONE, return_scalar = False): """ Dot product of two input vectors. @@ -173,10 +173,13 @@ def dot(lhs, rhs, lhs_opts=MATPROP.NONE, rhs_opts=MATPROP.NONE): - af.MATPROP.NONE - If no op should be done on `rhs`. - No other options are currently supported. + return_scalar: optional: bool. default: False. + - When set to true, the input arrays are flattened and the output is a scalar + Returns ------- - out : af.Array + out : af.Array or scalar Output of dot product of `lhs` and `rhs`. Note @@ -186,7 +189,122 @@ def dot(lhs, rhs, lhs_opts=MATPROP.NONE, rhs_opts=MATPROP.NONE): - Batches are not supported. """ - out = Array() - safe_call(backend.get().af_dot(ct.pointer(out.arr), lhs.arr, rhs.arr, - lhs_opts.value, rhs_opts.value)) + if return_scalar: + real = c_double_t(0) + imag = c_double_t(0) + safe_call(backend.get().af_dot_all(c_pointer(real), c_pointer(imag), + lhs.arr, rhs.arr, lhs_opts.value, rhs_opts.value)) + real = real.value + imag = imag.value + return real if imag == 0 else real + imag * 1j + else: + out = Array() + safe_call(backend.get().af_dot(c_pointer(out.arr), lhs.arr, rhs.arr, + lhs_opts.value, rhs_opts.value)) + return out + +def gemm(lhs, rhs, alpha=1.0, beta=0.0, lhs_opts=MATPROP.NONE, rhs_opts=MATPROP.NONE, C=None): + """ + BLAS general matrix multiply (GEMM) of two af_array objects. + + This provides a general interface to the BLAS level 3 general matrix multiply (GEMM), which is generally defined as: + + C = alpha * opA(A) opB(B) + beta * C + + where alpha and beta are both scalars; A and B are the matrix multiply operands; + and opA and opB are noop (if AF_MAT_NONE) or transpose (if AF_MAT_TRANS) operations + on A or B before the actual GEMM operation. + Batched GEMM is supported if at least either A or B have more than two dimensions + (see af::matmul for more details on broadcasting). + However, only one alpha and one beta can be used for all of the batched matrix operands. + + Parameters + ---------- + + lhs : af.Array + A 2 dimensional, real or complex arrayfire array. + + rhs : af.Array + A 2 dimensional, real or complex arrayfire array. + + alpha : scalar + + beta : scalar + + lhs_opts: optional: af.MATPROP. default: af.MATPROP.NONE. + Can be one of + - af.MATPROP.NONE - If no op should be done on `lhs`. + - af.MATPROP.TRANS - If `lhs` has to be transposed before multiplying. + - af.MATPROP.CTRANS - If `lhs` has to be hermitian transposed before multiplying. + + rhs_opts: optional: af.MATPROP. default: af.MATPROP.NONE. + Can be one of + - af.MATPROP.NONE - If no op should be done on `rhs`. + - af.MATPROP.TRANS - If `rhs` has to be transposed before multiplying. + - af.MATPROP.CTRANS - If `rhs` has to be hermitian transposed before multiplying. + + Returns + ------- + + out : af.Array + Output of the matrix multiplication on `lhs` and `rhs`. + + Note + ----- + + - The data types of `lhs` and `rhs` should be the same. + - Batches are not supported. + + """ + if C is None: + out = Array() + else: + out = C + + ltype = lhs.dtype() + + if ltype == Dtype.f32: + aptr = c_cast(c_pointer(c_float_t(alpha)),c_void_ptr_t) + bptr = c_cast(c_pointer(c_float_t(beta)), c_void_ptr_t) + elif ltype == Dtype.c32: + if isinstance(alpha, af_cfloat_t): + aptr = c_cast(c_pointer(alpha), c_void_ptr_t) + elif isinstance(alpha, tuple): + aptr = c_cast(c_pointer(af_cfloat_t(alpha[0], alpha[1])), c_void_ptr_t) + else: + aptr = c_cast(c_pointer(af_cfloat_t(alpha)), c_void_ptr_t) + + if isinstance(beta, af_cfloat_t): + bptr = c_cast(c_pointer(beta), c_void_ptr_t) + elif isinstance(beta, tuple): + bptr = c_cast(c_pointer(af_cfloat_t(beta[0], beta[1])), c_void_ptr_t) + else: + bptr = c_cast(c_pointer(af_cfloat_t(beta)), c_void_ptr_t) + + elif ltype == Dtype.f64: + aptr = c_cast(c_pointer(c_double_t(alpha)),c_void_ptr_t) + bptr = c_cast(c_pointer(c_double_t(beta)), c_void_ptr_t) + elif ltype == Dtype.c64: + if isinstance(alpha, af_cdouble_t): + aptr = c_cast(c_pointer(alpha), c_void_ptr_t) + elif isinstance(alpha, tuple): + aptr = c_cast(c_pointer(af_cdouble_t(alpha[0], alpha[1])), c_void_ptr_t) + else: + aptr = c_cast(c_pointer(af_cdouble_t(alpha)), c_void_ptr_t) + + if isinstance(beta, af_cdouble_t): + bptr = c_cast(c_pointer(beta), c_void_ptr_t) + elif isinstance(beta, tuple): + bptr = c_cast(c_pointer(af_cdouble_t(beta[0], beta[1])), c_void_ptr_t) + else: + bptr = c_cast(c_pointer(af_cdouble_t(beta)), c_void_ptr_t) + elif ltype == Dtype.f16: + raise TypeError("fp16 currently unsupported gemm() input type") + else: + raise TypeError("unsupported input type") + + + safe_call(backend.get().af_gemm(c_pointer(out.arr), + lhs_opts.value, rhs_opts.value, + aptr, lhs.arr, rhs.arr, bptr)) return out diff --git a/arrayfire/cuda.py b/arrayfire/cuda.py index 4ca7810a7..e5ef819be 100644 --- a/arrayfire/cuda.py +++ b/arrayfire/cuda.py @@ -35,8 +35,8 @@ def get_stream(idx): if (backend.name() != "cuda"): raise RuntimeError("Invalid backend loaded") - stream = ct.c_void_p(0) - safe_call(backend.get().afcu_get_stream(ct.pointer(stream), idx)) + stream = c_void_ptr_t(0) + safe_call(backend.get().afcu_get_stream(c_pointer(stream), idx)) return stream.value def get_native_id(idx): @@ -61,8 +61,8 @@ def get_native_id(idx): if (backend.name() != "cuda"): raise RuntimeError("Invalid backend loaded") - native = ct.c_int(0) - safe_call(backend.get().afcu_get_native_id(ct.pointer(native), idx)) + native = c_int_t(0) + safe_call(backend.get().afcu_get_native_id(c_pointer(native), idx)) return native.value def set_native_id(idx): @@ -85,3 +85,10 @@ def set_native_id(idx): safe_call(backend.get().afcu_set_native_id(idx)) return + +def set_cublas_mode(mode=CUBLAS_MATH_MODE.DEFAULT): + """ + Set's cuBLAS math mode for CUDA backend. In other backends, this has no effect. + """ + safe_call(backend().get().afcu_cublasSetMathMode(mode.value)) + return diff --git a/arrayfire/data.py b/arrayfire/data.py index 2e6c9fc01..1fbe17a53 100644 --- a/arrayfire/data.py +++ b/arrayfire/data.py @@ -16,6 +16,7 @@ from .array import * from .util import * from .util import _is_number +from .random import randu, randn, set_seed, get_seed def constant(val, d0, d1=None, d2=None, d3=None, dtype=Dtype.f32): """ @@ -117,7 +118,7 @@ def range(d0, d1=None, d2=None, d3=None, dim=0, dtype=Dtype.f32): out = Array() dims = dim4(d0, d1, d2, d3) - safe_call(backend.get().af_range(ct.pointer(out.arr), 4, ct.pointer(dims), dim, dtype.value)) + safe_call(backend.get().af_range(c_pointer(out.arr), 4, c_pointer(dims), dim, dtype.value)) return out @@ -182,109 +183,10 @@ def iota(d0, d1=None, d2=None, d3=None, dim=-1, tile_dims=None, dtype=Dtype.f32) tdims = dim4(td[0], td[1], td[2], td[3]) - safe_call(backend.get().af_iota(ct.pointer(out.arr), 4, ct.pointer(dims), - 4, ct.pointer(tdims), dtype.value)) + safe_call(backend.get().af_iota(c_pointer(out.arr), 4, c_pointer(dims), + 4, c_pointer(tdims), dtype.value)) return out -def randu(d0, d1=None, d2=None, d3=None, dtype=Dtype.f32): - """ - Create a multi dimensional array containing values from a uniform distribution. - - Parameters - ---------- - d0 : int. - Length of first dimension. - - d1 : optional: int. default: None. - Length of second dimension. - - d2 : optional: int. default: None. - Length of third dimension. - - d3 : optional: int. default: None. - Length of fourth dimension. - - dtype : optional: af.Dtype. default: af.Dtype.f32. - Data type of the array. - - Returns - ------- - - out : af.Array - Multi dimensional array whose elements are sampled uniformly between [0, 1]. - - If d1 is None, `out` is 1D of size (d0,). - - If d1 is not None and d2 is None, `out` is 2D of size (d0, d1). - - If d1 and d2 are not None and d3 is None, `out` is 3D of size (d0, d1, d2). - - If d1, d2, d3 are all not None, `out` is 4D of size (d0, d1, d2, d3). - """ - out = Array() - dims = dim4(d0, d1, d2, d3) - - safe_call(backend.get().af_randu(ct.pointer(out.arr), 4, ct.pointer(dims), dtype.value)) - return out - -def randn(d0, d1=None, d2=None, d3=None, dtype=Dtype.f32): - """ - Create a multi dimensional array containing values from a normal distribution. - - Parameters - ---------- - d0 : int. - Length of first dimension. - - d1 : optional: int. default: None. - Length of second dimension. - - d2 : optional: int. default: None. - Length of third dimension. - - d3 : optional: int. default: None. - Length of fourth dimension. - - dtype : optional: af.Dtype. default: af.Dtype.f32. - Data type of the array. - - Returns - ------- - - out : af.Array - Multi dimensional array whose elements are sampled from a normal distribution with mean 0 and sigma of 1. - - If d1 is None, `out` is 1D of size (d0,). - - If d1 is not None and d2 is None, `out` is 2D of size (d0, d1). - - If d1 and d2 are not None and d3 is None, `out` is 3D of size (d0, d1, d2). - - If d1, d2, d3 are all not None, `out` is 4D of size (d0, d1, d2, d3). - """ - - out = Array() - dims = dim4(d0, d1, d2, d3) - - safe_call(backend.get().af_randn(ct.pointer(out.arr), 4, ct.pointer(dims), dtype.value)) - return out - -def set_seed(seed=0): - """ - Set the seed for the random number generator. - - Parameters - ---------- - seed: int. - Seed for the random number generator - """ - safe_call(backend.get().af_set_seed(ct.c_ulonglong(seed))) - -def get_seed(): - """ - Get the seed for the random number generator. - - Returns - ---------- - seed: int. - Seed for the random number generator - """ - seed = ct.c_ulonglong(0) - safe_call(backend.get().af_get_seed(ct.pointer(seed))) - return seed.value - def identity(d0, d1, d2=None, d3=None, dtype=Dtype.f32): """ Create an identity matrix or batch of identity matrices. @@ -319,7 +221,7 @@ def identity(d0, d1, d2=None, d3=None, dtype=Dtype.f32): out = Array() dims = dim4(d0, d1, d2, d3) - safe_call(backend.get().af_identity(ct.pointer(out.arr), 4, ct.pointer(dims), dtype.value)) + safe_call(backend.get().af_identity(c_pointer(out.arr), 4, c_pointer(dims), dtype.value)) return out def diag(a, num=0, extract=True): @@ -350,9 +252,9 @@ def diag(a, num=0, extract=True): """ out = Array() if extract: - safe_call(backend.get().af_diag_extract(ct.pointer(out.arr), a.arr, ct.c_int(num))) + safe_call(backend.get().af_diag_extract(c_pointer(out.arr), a.arr, c_int_t(num))) else: - safe_call(backend.get().af_diag_create(ct.pointer(out.arr), a.arr, ct.c_int(num))) + safe_call(backend.get().af_diag_create(c_pointer(out.arr), a.arr, c_int_t(num))) return out def join(dim, first, second, third=None, fourth=None): @@ -384,7 +286,7 @@ def join(dim, first, second, third=None, fourth=None): An array containing the input arrays joined along the specified dimension. Examples - ------- + --------- >>> import arrayfire as af >>> a = af.randu(2, 3) @@ -415,9 +317,9 @@ def join(dim, first, second, third=None, fourth=None): """ out = Array() if (third is None and fourth is None): - safe_call(backend.get().af_join(ct.pointer(out.arr), dim, first.arr, second.arr)) + safe_call(backend.get().af_join(c_pointer(out.arr), dim, first.arr, second.arr)) else: - c_void_p_4 = ct.c_void_p * 4 + c_void_p_4 = c_void_ptr_t * 4 c_array_vec = c_void_p_4(first.arr, second.arr, 0, 0) num = 2 if third is not None: @@ -427,7 +329,7 @@ def join(dim, first, second, third=None, fourth=None): c_array_vec[num] = fourth.arr num+=1 - safe_call(backend.get().af_join_many(ct.pointer(out.arr), dim, num, ct.pointer(c_array_vec))) + safe_call(backend.get().af_join_many(c_pointer(out.arr), dim, num, c_pointer(c_array_vec))) return out @@ -460,7 +362,7 @@ def tile(a, d0, d1=1, d2=1, d3=1): An array containing the input after tiling the the specified number of times. Examples - ------- + --------- >>> import arrayfire as af >>> a = af.randu(2, 3) @@ -492,10 +394,9 @@ def tile(a, d0, d1=1, d2=1, d3=1): 0.8224 0.1794 0.0081 0.8224 0.1794 0.0081 """ out = Array() - safe_call(backend.get().af_tile(ct.pointer(out.arr), a.arr, d0, d1, d2, d3)) + safe_call(backend.get().af_tile(c_pointer(out.arr), a.arr, d0, d1, d2, d3)) return out - def reorder(a, d0=1, d1=0, d2=2, d3=3): """ Reorder the dimensions of the input. @@ -577,7 +478,7 @@ def reorder(a, d0=1, d1=0, d2=2, d3=3): 0.9276 0.8662 0.3578 0.6263 0.9747 """ out = Array() - safe_call(backend.get().af_reorder(ct.pointer(out.arr), a.arr, d0, d1, d2, d3)) + safe_call(backend.get().af_reorder(c_pointer(out.arr), a.arr, d0, d1, d2, d3)) return out def shift(a, d0, d1=0, d2=0, d3=0): @@ -633,7 +534,7 @@ def shift(a, d0, d1=0, d2=0, d3=0): 0.1437 0.0899 0.7104 """ out = Array() - safe_call(backend.get().af_shift(ct.pointer(out.arr), a.arr, d0, d1, d2, d3)) + safe_call(backend.get().af_shift(c_pointer(out.arr), a.arr, d0, d1, d2, d3)) return out def moddims(a, d0, d1=1, d2=1, d3=1): @@ -667,7 +568,7 @@ def moddims(a, d0, d1=1, d2=1, d3=1): """ out = Array() dims = dim4(d0, d1, d2, d3) - safe_call(backend.get().af_moddims(ct.pointer(out.arr), a.arr, 4, ct.pointer(dims))) + safe_call(backend.get().af_moddims(c_pointer(out.arr), a.arr, 4, c_pointer(dims))) return out def flat(a): @@ -687,7 +588,7 @@ def flat(a): - 1 dimensional array containing all the elements from `a`. """ out = Array() - safe_call(backend.get().af_flat(ct.pointer(out.arr), a.arr)) + safe_call(backend.get().af_flat(c_pointer(out.arr), a.arr)) return out def flip(a, dim=0): @@ -710,7 +611,7 @@ def flip(a, dim=0): The output after flipping `a` along `dim`. Examples - ------- + --------- >>> import arrayfire as af >>> a = af.randu(3, 3) @@ -734,7 +635,7 @@ def flip(a, dim=0): """ out = Array() - safe_call(backend.get().af_flip(ct.pointer(out.arr), a.arr, ct.c_int(dim))) + safe_call(backend.get().af_flip(c_pointer(out.arr), a.arr, c_int_t(dim))) return out def lower(a, is_unit_diag=False): @@ -757,7 +658,7 @@ def lower(a, is_unit_diag=False): An array containing the lower triangular elements from `a`. """ out = Array() - safe_call(backend.get().af_lower(ct.pointer(out.arr), a.arr, is_unit_diag)) + safe_call(backend.get().af_lower(c_pointer(out.arr), a.arr, is_unit_diag)) return out def upper(a, is_unit_diag=False): @@ -780,7 +681,7 @@ def upper(a, is_unit_diag=False): An array containing the upper triangular elements from `a`. """ out = Array() - safe_call(backend.get().af_upper(ct.pointer(out.arr), a.arr, is_unit_diag)) + safe_call(backend.get().af_upper(c_pointer(out.arr), a.arr, is_unit_diag)) return out def select(cond, lhs, rhs): @@ -841,12 +742,12 @@ def select(cond, lhs, rhs): raise TypeError("Atleast one input needs to be of type arrayfire.array") elif (is_left_array and is_right_array): - safe_call(backend.get().af_select(ct.pointer(out.arr), cond.arr, lhs.arr, rhs.arr)) + safe_call(backend.get().af_select(c_pointer(out.arr), cond.arr, lhs.arr, rhs.arr)) elif (_is_number(rhs)): - safe_call(backend.get().af_select_scalar_r(ct.pointer(out.arr), cond.arr, lhs.arr, ct.c_double(rhs))) + safe_call(backend.get().af_select_scalar_r(c_pointer(out.arr), cond.arr, lhs.arr, c_double_t(rhs))) else: - safe_call(backend.get().af_select_scalar_l(ct.pointer(out.arr), cond.arr, ct.c_double(lhs), rhs.arr)) + safe_call(backend.get().af_select_scalar_l(c_pointer(out.arr), cond.arr, c_double_t(lhs), rhs.arr)) return out @@ -896,4 +797,103 @@ def replace(lhs, cond, rhs): if (is_right_array): safe_call(backend.get().af_replace(lhs.arr, cond.arr, rhs.arr)) else: - safe_call(backend.get().af_replace_scalar(lhs.arr, cond.arr, ct.c_double(rhs))) + safe_call(backend.get().af_replace_scalar(lhs.arr, cond.arr, c_double_t(rhs))) + +def pad(a, beginPadding, endPadding, padFillType = PAD.ZERO): + """ + Pad an array + + This function will pad an array with the specified border size. + Newly padded values can be filled in several different ways. + + Parameters + ---------- + + a: af.Array + A multi dimensional input arrayfire array. + + beginPadding: tuple of ints. default: (0, 0, 0, 0). + + endPadding: tuple of ints. default: (0, 0, 0, 0). + + padFillType: optional af.PAD default: af.PAD.ZERO + specifies type of values to fill padded border with + + Returns + ------- + output: af.Array + A padded array + + Examples + --------- + >>> import arrayfire as af + >>> a = af.randu(3,3) + >>> af.display(a) + [3 3 1 1] + 0.4107 0.1794 0.3775 + 0.8224 0.4198 0.3027 + 0.9518 0.0081 0.6456 + + >>> padded = af.pad(a, (1, 1), (1, 1), af.ZERO) + >>> af.display(padded) + [5 5 1 1] + 0.0000 0.0000 0.0000 0.0000 0.0000 + 0.0000 0.4107 0.1794 0.3775 0.0000 + 0.0000 0.8224 0.4198 0.3027 0.0000 + 0.0000 0.9518 0.0081 0.6456 0.0000 + 0.0000 0.0000 0.0000 0.0000 0.0000 + """ + out = Array() + begin_dims = dim4(beginPadding[0], beginPadding[1], beginPadding[2], beginPadding[3]) + end_dims = dim4(endPadding[0], endPadding[1], endPadding[2], endPadding[3]) + + safe_call(backend.get().af_pad(c_pointer(out.arr), a.arr, 4, c_pointer(begin_dims), 4, c_pointer(end_dims), padFillType.value)) + return out + + +def lookup(a, idx, dim=0): + """ + Lookup the values of input array based on index. + + Parameters + ---------- + + a : af.Array. + Multi dimensional array. + + idx : is lookup indices + + dim : optional: int. default: 0. + Specifies the dimension for indexing + + Returns + ------- + + out : af.Array + An array containing values at locations specified by 'idx' + + Examples + --------- + + >>> import arrayfire as af + >>> arr = af.Array([1,0,3,4,5,6], (2,3)) + >>> af.display(arr) + [2 3 1 1] + 1.0000 3.0000 5.0000 + 0.0000 4.0000 6.0000 + + >>> idx = af.array([0, 2]) + >>> af.lookup(arr, idx, 1) + [2 2 1 1] + 1.0000 5.0000 + 0.0000 6.0000 + + >>> idx = af.array([0]) + >>> af.lookup(arr, idx, 0) + [2 1 1 1] + 0.0000 + 2.0000 + """ + out = Array() + safe_call(backend.get().af_lookup(c_pointer(out.arr), a.arr, idx.arr, c_int_t(dim))) + return out diff --git a/arrayfire/device.py b/arrayfire/device.py index f84404f07..53f302db5 100644 --- a/arrayfire/device.py +++ b/arrayfire/device.py @@ -39,14 +39,14 @@ def device_info(): - 'toolkit': The toolkit version for the backend. - 'compute': The compute version of the device. """ - c_char_256 = ct.c_char * 256 + c_char_256 = c_char_t * 256 device_name = c_char_256() backend_name = c_char_256() toolkit = c_char_256() compute = c_char_256() - safe_call(backend.get().af_device_info(ct.pointer(device_name), ct.pointer(backend_name), - ct.pointer(toolkit), ct.pointer(compute))) + safe_call(backend.get().af_device_info(c_pointer(device_name), c_pointer(backend_name), + c_pointer(toolkit), c_pointer(compute))) dev_info = {} dev_info['device'] = to_str(device_name) dev_info['backend'] = to_str(backend_name) @@ -59,16 +59,16 @@ def get_device_count(): """ Returns the number of devices available. """ - c_num = ct.c_int(0) - safe_call(backend.get().af_get_device_count(ct.pointer(c_num))) + c_num = c_int_t(0) + safe_call(backend.get().af_get_device_count(c_pointer(c_num))) return c_num.value def get_device(): """ Returns the id of the current device. """ - c_dev = ct.c_int(0) - safe_call(backend.get().af_get_device(ct.pointer(c_dev))) + c_dev = c_int_t(0) + safe_call(backend.get().af_get_device(c_pointer(c_dev))) return c_dev.value def set_device(num): @@ -76,7 +76,7 @@ def set_device(num): Change the active device to the specified id. Parameters - --------- + ----------- num: int. id of the desired device. """ @@ -136,7 +136,7 @@ def is_dbl_supported(device=None): Check if double precision is supported on specified device. Parameters - --------- + ----------- device: optional: int. default: None. id of the desired device. @@ -146,8 +146,27 @@ def is_dbl_supported(device=None): - False if double precision not supported. """ dev = device if device is not None else get_device() - res = ct.c_bool(False) - safe_call(backend.get().af_get_dbl_support(ct.pointer(res), dev)) + res = c_bool_t(False) + safe_call(backend.get().af_get_dbl_support(c_pointer(res), dev)) + return res.value + +def is_half_supported(device=None): + """ + Check if half precision is supported on specified device. + + Parameters + ----------- + device: optional: int. default: None. + id of the desired device. + + Returns + -------- + - True if half precision supported. + - False if half precision not supported. + """ + dev = device if device is not None else get_device() + res = c_bool_t(False) + safe_call(backend.get().af_get_half_support(c_pointer(res), dev)) return res.value def sync(device=None): @@ -155,7 +174,7 @@ def sync(device=None): Block until all the functions on the device have completed execution. Parameters - --------- + ----------- device: optional: int. default: None. id of the desired device. """ @@ -163,24 +182,87 @@ def sync(device=None): safe_call(backend.get().af_sync(dev)) def __eval(*args): - for A in args: - if isinstance(A, tuple): - __eval(*A) - if isinstance(A, list): - __eval(*A) - if isinstance(A, Array): - safe_call(backend.get().af_eval(A.arr)) + nargs = len(args) + if (nargs == 1): + safe_call(backend.get().af_eval(args[0].arr)) + else: + c_void_p_n = c_void_ptr_t * nargs + arrs = c_void_p_n() + for n in range(nargs): + arrs[n] = args[n].arr + safe_call(backend.get().af_eval_multiple(c_int_t(nargs), c_pointer(arrs))) + return def eval(*args): """ - Evaluate the input + Evaluate one or more inputs together Parameters ----------- args : arguments to be evaluated + + Note + ----- + + All the input arrays to this function should be of the same size. + + Examples + -------- + + >>> a = af.constant(1, 3, 3) + >>> b = af.constant(2, 3, 3) + >>> c = a + b + >>> d = a - b + >>> af.eval(c, d) # A single kernel is launched here + >>> c + arrayfire.Array() + Type: float + [3 3 1 1] + 3.0000 3.0000 3.0000 + 3.0000 3.0000 3.0000 + 3.0000 3.0000 3.0000 + + >>> d + arrayfire.Array() + Type: float + [3 3 1 1] + -1.0000 -1.0000 -1.0000 + -1.0000 -1.0000 -1.0000 + -1.0000 -1.0000 -1.0000 + """ + for arg in args: + if not isinstance(arg, Array): + raise RuntimeError("All inputs to eval must be of type arrayfire.Array") + + __eval(*args) + +def set_manual_eval_flag(flag): """ + Tells the backend JIT engine to disable heuristics for determining when to evaluate a JIT tree. - __eval(args) + Parameters + ---------- + + flag : optional: bool. + - Specifies if the heuristic evaluation of the JIT tree needs to be disabled. + + Note + ---- + This does not affect the evaluation that occurs when a non JIT function forces the evaluation. + """ + safe_call(backend.get().af_set_manual_eval_flag(flag)) + +def get_manual_eval_flag(): + """ + Query the backend JIT engine to see if the user disabled heuristic evaluation of the JIT tree. + + Note + ---- + This does not affect the evaluation that occurs when a non JIT function forces the evaluation. + """ + res = c_bool_t(False) + safe_call(backend.get().af_get_manual_eval_flag(c_pointer(res))) + return res.value def device_mem_info(): """ @@ -199,17 +281,63 @@ def device_mem_info(): - The difference between alloc bytes and lock bytes equals the number of free bytes. """ - alloc_bytes = ct.c_size_t(0) - alloc_buffers = ct.c_size_t(0) - lock_bytes = ct.c_size_t(0) - lock_buffers = ct.c_size_t(0) - safe_call(backend.get().af_device_mem_info(ct.pointer(alloc_bytes), ct.pointer(alloc_buffers), - ct.pointer(lock_bytes), ct.pointer(lock_buffers))) + alloc_bytes = c_size_t(0) + alloc_buffers = c_size_t(0) + lock_bytes = c_size_t(0) + lock_buffers = c_size_t(0) + safe_call(backend.get().af_device_mem_info(c_pointer(alloc_bytes), c_pointer(alloc_buffers), + c_pointer(lock_bytes), c_pointer(lock_buffers))) mem_info = {} mem_info['alloc'] = {'buffers' : alloc_buffers.value, 'bytes' : alloc_bytes.value} mem_info['lock'] = {'buffers' : lock_buffers.value, 'bytes' : lock_bytes.value} return mem_info +def print_mem_info(title = "Memory Info", device_id = None): + """ + Prints the memory used for the specified device. + + Parameters + ---------- + title: optional. Default: "Memory Info" + - Title to display before printing the memory info. + device_id: optional. Default: None + - Specifies the device for which the memory info should be displayed. + - If None, uses the current device. + + Examples + -------- + + >>> a = af.randu(5,5) + >>> af.print_mem_info() + Memory Info + --------------------------------------------------------- + | POINTER | SIZE | AF LOCK | USER LOCK | + --------------------------------------------------------- + | 0x706400000 | 1 KB | Yes | No | + --------------------------------------------------------- + >>> b = af.randu(5,5) + >>> af.print_mem_info() + Memory Info + --------------------------------------------------------- + | POINTER | SIZE | AF LOCK | USER LOCK | + --------------------------------------------------------- + | 0x706400400 | 1 KB | Yes | No | + | 0x706400000 | 1 KB | Yes | No | + --------------------------------------------------------- + >>> a = af.randu(1000,1000) + >>> af.print_mem_info() + Memory Info + --------------------------------------------------------- + | POINTER | SIZE | AF LOCK | USER LOCK | + --------------------------------------------------------- + | 0x706500000 | 3.815 MB | Yes | No | + | 0x706400400 | 1 KB | Yes | No | + | 0x706400000 | 1 KB | No | No | + --------------------------------------------------------- + """ + device_id = device_id if device_id else get_device() + safe_call(backend.get().af_print_mem_info(title.encode('utf-8'), device_id)) + def device_gc(): """ Ask the garbage collector to free all unlocked memory @@ -235,11 +363,19 @@ def get_device_ptr(a): - This function enables the user to interoperate arrayfire with other CUDA/OpenCL/C libraries. """ - ptr = ct.c_void_p(0) - safe_call(backend.get().af_get_device_ptr(ct.pointer(ptr), a.arr)) + ptr = c_void_ptr_t(0) + safe_call(backend.get().af_get_device_ptr(c_pointer(ptr), a.arr)) return ptr def lock_device_ptr(a): + """ + This functions is deprecated. Please use lock_array instead. + """ + import warnings + warnings.warn("This function is deprecated. Use lock_array instead.", DeprecationWarning) + lock_array(a) + +def lock_array(a): """ Ask arrayfire to not perform garbage collection on raw data held by an array. @@ -250,12 +386,36 @@ def lock_device_ptr(a): Note ----- - - The device pointer of `a` is not freed by memory manager until `unlock_device_ptr()` is called. + - The device pointer of `a` is not freed by memory manager until `unlock_array()` is called. + """ + safe_call(backend.get().af_lock_array(a.arr)) + +def is_locked_array(a): """ - ptr = ct.c_void_p(0) - safe_call(backend.get().af_lock_device_ptr(a.arr)) + Check if the input array is locked by the user. + + Parameters + ---------- + a: af.Array + - A multi dimensional arrayfire array. + + Returns + ----------- + A bool specifying if the input array is locked. + """ + res = c_bool_t(False) + safe_call(backend.get().af_is_locked_array(c_pointer(res), a.arr)) + return res.value def unlock_device_ptr(a): + """ + This functions is deprecated. Please use unlock_array instead. + """ + import warnings + warnings.warn("This function is deprecated. Use unlock_array instead.", DeprecationWarning) + unlock_array(a) + +def unlock_array(a): """ Tell arrayfire to resume garbage collection on raw data held by an array. @@ -265,7 +425,54 @@ def unlock_device_ptr(a): - A multi dimensional arrayfire array. """ - ptr = ct.c_void_p(0) - safe_call(backend.get().af_unlock_device_ptr(a.arr)) + safe_call(backend.get().af_unlock_array(a.arr)) + +def alloc_device(num_bytes): + """ + Allocate a buffer on the device with specified number of bytes. + """ + ptr = c_void_ptr_t(0) + c_num_bytes = c_dim_t(num_bytes) + safe_call(backend.get().af_alloc_device(c_pointer(ptr), c_num_bytes)) + return ptr.value + +def alloc_host(num_bytes): + """ + Allocate a buffer on the host with specified number of bytes. + """ + ptr = c_void_ptr_t(0) + c_num_bytes = c_dim_t(num_bytes) + safe_call(backend.get().af_alloc_host(c_pointer(ptr), c_num_bytes)) + return ptr.value + +def alloc_pinned(num_bytes): + """ + Allocate a buffer on the host using pinned memory with specified number of bytes. + """ + ptr = c_void_ptr_t(0) + c_num_bytes = c_dim_t(num_bytes) + safe_call(backend.get().af_alloc_pinned(c_pointer(ptr), c_num_bytes)) + return ptr.value + +def free_device(ptr): + """ + Free the device memory allocated by alloc_device + """ + cptr = c_void_ptr_t(ptr) + safe_call(backend.get().af_free_device(cptr)) + +def free_host(ptr): + """ + Free the host memory allocated by alloc_host + """ + cptr = c_void_ptr_t(ptr) + safe_call(backend.get().af_free_host(cptr)) + +def free_pinned(ptr): + """ + Free the pinned memory allocated by alloc_pinned + """ + cptr = c_void_ptr_t(ptr) + safe_call(backend.get().af_free_pinned(cptr)) from .array import Array diff --git a/arrayfire/features.py b/arrayfire/features.py index 190bdde40..2f76a97eb 100644 --- a/arrayfire/features.py +++ b/arrayfire/features.py @@ -6,9 +6,11 @@ # The complete license agreement can be obtained at: # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## + """ -arrayfire.Features class +Features class used for Computer Vision algorithms. """ + from .library import * from .array import * import numbers @@ -25,55 +27,63 @@ class Features(object): """ def __init__(self, num=0): - self.feat = ct.c_void_p(0) + self.feat = c_void_ptr_t(0) if num is not None: assert(isinstance(num, numbers.Number)) - safe_call(backend.get().af_create_features(ct.pointer(self.feat), ct.c_longlong(num))) + safe_call(backend.get().af_create_features(c_pointer(self.feat), c_dim_t(num))) + + def __del__(self): + """ + Release features' memory + """ + if self.feat: + backend.get().af_release_features(self.feat) + self.feat = None - def num_features(): + def num_features(self): """ Returns the number of features detected. """ - num = ct.c_longlong(0) - safe_call(backend.get().af_get_features_num(ct.pointer(num), self.feat)) + num = c_dim_t(0) + safe_call(backend.get().af_get_features_num(c_pointer(num), self.feat)) return num - def get_xpos(): + def get_xpos(self): """ Returns the x-positions of the features detected. """ out = Array() - safe_call(backend.get().af_get_features_xpos(ct.pointer(out.arr), self.feat)) + safe_call(backend.get().af_get_features_xpos(c_pointer(out.arr), self.feat)) return out - def get_ypos(): + def get_ypos(self): """ Returns the y-positions of the features detected. """ out = Array() - safe_call(backend.get().af_get_features_ypos(ct.pointer(out.arr), self.feat)) + safe_call(backend.get().af_get_features_ypos(c_pointer(out.arr), self.feat)) return out - def get_score(): + def get_score(self): """ Returns the scores of the features detected. """ out = Array() - safe_call(backend.get().af_get_features_score(ct.pointer(out.arr), self.feat)) + safe_call(backend.get().af_get_features_score(c_pointer(out.arr), self.feat)) return out - def get_orientation(): + def get_orientation(self): """ Returns the orientations of the features detected. """ out = Array() - safe_call(backend.get().af_get_features_orientation(ct.pointer(out.arr), self.feat)) + safe_call(backend.get().af_get_features_orientation(c_pointer(out.arr), self.feat)) return out - def get_size(): + def get_size(self): """ Returns the sizes of the features detected. """ out = Array() - safe_call(backend.get().af_get_features_size(ct.pointer(out.arr), self.feat)) + safe_call(backend.get().af_get_features_size(c_pointer(out.arr), self.feat)) return out diff --git a/arrayfire/graphics.py b/arrayfire/graphics.py index b1b4f821a..70881f42c 100644 --- a/arrayfire/graphics.py +++ b/arrayfire/graphics.py @@ -8,7 +8,7 @@ ######################################################## """ -graphics functions for arrayfire +Graphics functions (plot, image, etc). """ from .library import * @@ -16,15 +16,15 @@ from .util import _is_number class _Cell(ct.Structure): - _fields_ = [("row", ct.c_int), - ("col", ct.c_int), - ("title", ct.c_char_p), - ("cmap", ct.c_int)] + _fields_ = [("row", c_int_t), + ("col", c_int_t), + ("title", c_char_ptr_t), + ("cmap", c_int_t)] def __init__(self, r, c, title, cmap): self.row = r self.col = c - self.title = title if title is not None else ct.c_char_p() + self.title = title if title is not None else c_char_ptr_t() self.cmap = cmap.value class Window(object): @@ -48,7 +48,7 @@ class Window(object): def __init__(self, width=1280, height=720, title="ArrayFire"): self._r = -1 self._c = -1 - self._wnd = ct.c_longlong(0) + self._wnd = c_void_ptr_t(0) self._cmap = COLORMAP.DEFAULT _width = 1280 if width is None else width @@ -57,9 +57,9 @@ def __init__(self, width=1280, height=720, title="ArrayFire"): _title = _title.encode("ascii") - safe_call(backend.get().af_create_window(ct.pointer(self._wnd), - ct.c_int(_width), ct.c_int(_height), - ct.c_char_p(_title))) + safe_call(backend.get().af_create_window(c_pointer(self._wnd), + c_int_t(_width), c_int_t(_height), + c_char_ptr_t(_title))) def __del__(self): """ @@ -81,7 +81,7 @@ def set_pos(self, x, y): Pixel offset from top """ - safe_call(backend.get().af_set_position(self._wnd, ct.c_int(x), ct.c_int(y))) + safe_call(backend.get().af_set_position(self._wnd, c_int_t(x), c_int_t(y))) def set_title(self, title): """ @@ -127,8 +127,8 @@ def image(self, img, title=None): """ Display an arrayfire array as an image. - Paramters - --------- + Parameters + ---------- img: af.Array. A 2 dimensional array for single channel image. @@ -138,14 +138,14 @@ def image(self, img, title=None): Title used for the image. """ _cell = _Cell(self._r, self._c, title, self._cmap) - safe_call(backend.get().af_draw_image(self._wnd, img.arr, ct.pointer(_cell))) + safe_call(backend.get().af_draw_image(self._wnd, img.arr, c_pointer(_cell))) - def plot(self, X, Y, title=None): + def scatter(self, X, Y, Z=None, points=None, marker=MARKER.POINT, title=None): """ - Display a 2D Plot. + Renders input arrays as 2D or 3D scatter plot. - Paramters - --------- + Parameters + ---------- X: af.Array. A 1 dimensional array containing X co-ordinates. @@ -153,34 +153,219 @@ def plot(self, X, Y, title=None): Y: af.Array. A 1 dimensional array containing Y co-ordinates. + Z: optional: af.Array. default: None. + - A 1 dimensional array containing Z co-ordinates. + - Not used if line is not None + + points: optional: af.Array. default: None. + - A 2 dimensional array of size [n 2]. Each column denotes X and Y co-ordinates for 2D scatter plot. + - A 3 dimensional array of size [n 3]. Each column denotes X, Y, and Z co-ordinates for 3D scatter plot. + + marker: af.MARKER + Specifies how the points look + title: str. Title used for the plot. """ _cell = _Cell(self._r, self._c, title, self._cmap) - safe_call(backend.get().af_draw_plot(self._wnd, X.arr, Y.arr, ct.pointer(_cell))) - def plot3(self, line, title=None): + if points is None: + if Z is None: + safe_call(backend.get().af_draw_scatter_2d(self._wnd, X.arr, Y.arr, + marker.value, c_pointer(_cell))) + else: + safe_call(backend.get().af_draw_scatter_3d(self._wnd, X.arr, Y.arr, Z.arr, + marker.value, c_pointer(_cell))) + else: + safe_call(backend.get().af_draw_scatter_nd(self._wnd, points.arr, marker.value, c_pointer(_cell))) + + def scatter2(self, points, marker=MARKER.POINT, title=None): """ - Renders the input array as a 3D line plot. + Renders the input array as a 2D Scatter plot. - Paramters - --------- + Parameters + ---------- - line: af.Array. + points: af.Array. + A 2 dimensional array containing (X,Y) co-ordinates. + + marker: af.MARKER + Specifies how the points look + + title: str. + Title used for the plot. + """ + assert(points.numdims() == 2) + _cell = _Cell(self._r, self._c, title, self._cmap) + safe_call(backend.get().af_draw_scatter2(self._wnd, points.arr, + marker.value, c_pointer(_cell))) + + def scatter3(self, points, marker=MARKER.POINT, title=None): + """ + Renders the input array as a 3D Scatter plot. + + Parameters + ---------- + + points: af.Array. A 2 dimensional array containing (X,Y,Z) co-ordinates. + marker: af.MARKER + Specifies how the points look + + title: str. + Title used for the plot. + """ + assert(points.numdims() == 3) + _cell = _Cell(self._r, self._c, title, self._cmap) + safe_call(backend.get().af_draw_scatter3(self._wnd, points.arr, + marker.value, c_pointer(_cell))) + def plot(self, X, Y, Z=None, line = None, title=None): + """ + Display a 2D or 3D Plot. + + Parameters + ---------- + + X: af.Array. + - A 1 dimensional array containing X co-ordinates. + - Not used if line is not None + + Y: af.Array. + - A 1 dimensional array containing Y co-ordinates. + - Not used if line is not None + + Z: optional: af.Array. default: None. + - A 1 dimensional array containing Z co-ordinates. + - Not used if line is not None + + line: optional: af.Array. default: None. + - A 2 dimensional array of size [n 2]. Each column denotes X and Y co-ordinates for plotting 2D lines. + - A 3 dimensional array of size [n 3]. Each column denotes X, Y, and Z co-ordinates for plotting 3D lines. + + title: str. + Title used for the plot. + + Note + ---- + + The line parameter takes precedence. + """ + _cell = _Cell(self._r, self._c, title, self._cmap) + if line is None: + if Z is None: + safe_call(backend.get().af_draw_plot_2d(self._wnd, X.arr, Y.arr, c_pointer(_cell))) + else: + safe_call(backend.get().af_draw_plot_3d(self._wnd, X.arr, Y.arr, Z.arr, c_pointer(_cell))) + else: + safe_call(backend.get().af_draw_plot_nd(self._wnd, line.arr, c_pointer(_cell))) + + def plot2(self, line, title=None): + """ + Display a 2D Plot. + + Parameters + ---------- + + line: af.Array. + - A 2 dimensional array of size [n 2]. Each column denotes X, and Y co-ordinates for plotting 2D lines. + + title: str. + Title used for the plot. + + """ + + assert(line.numdims() == 2) + _cell = _Cell(self._r, self._c, title, self._cmap) + safe_call(backend.get().af_draw_plot_nd(self._wnd, line.arr, c_pointer(_cell))) + + def plot3(self, X=None, Y=None, Z=None, line=None, title=None): + """ + Display a 3D Plot. + + Parameters + ---------- + + line: af.Array. + - A 3 dimensional array of size [n 3]. Each column denotes X, Y, and Z co-ordinates for plotting 3D lines. + title: str. Title used for the plot. """ + + assert(line.numdims() == 3) + _cell = _Cell(self._r, self._c, title, self._cmap) + safe_call(backend.get().af_draw_plot_nd(self._wnd, line.arr, c_pointer(_cell))) + + def vector_field(self, xpoints, xdirs, ypoints, ydirs, zpoints=None, zdirs=None, + points = None, dirs = None, title=None): + """ + Display a 2D or 3D Vector_Field. + + Parameters + ---------- + + xpoints : af.Array. + - A 1 dimensional array containing X co-ordinates. + - Not used if points is not None + + xdirs : af.Array. + - A 1 dimensional array specifying direction at current location. + - Not used if dirs is not None + + ypoints : af.Array. + - A 1 dimensional array containing Y co-ordinates. + - Not used if points is not None + + ydirs : af.Array. + - A 1 dimensional array specifying direction at current location. + - Not used if dirs is not None + + zpoints : optional: af.Array. default: None. + - A 1 dimensional array containing Z co-ordinates. + - Not used if points is not None + + zdirs : optional: af.Array. default: none. + - A 1 dimensional array specifying direction at current location. + - Not used if dirs is not None + + points : optional: af.Array. default: None. + - A 2 dimensional array of size [n 2]. Each column denotes X and Y co-ordinates for plotting 2D lines. + - A 3 dimensional array of size [n 3]. Each column denotes X, Y, and Z co-ordinates for plotting 3D lines. + + dirs : optional: af.Array. default: None. + - A 2 dimensional array of size [n 2]. Each column denotes X and Y directions for plotting 2D lines. + - A 3 dimensional array of size [n 3]. Each column denotes X, Y, and Z directions for plotting 3D lines. + + title : str. + Title used for the plot. + + Note + ---- + + The line parameter takes precedence. + """ _cell = _Cell(self._r, self._c, title, self._cmap) - safe_call(backend.get().af_draw_plot3(self._wnd, line.arr, ct.pointer(_cell))) + if line is None: + if Z is None: + safe_call(backend.get().af_draw_vector_field_2d(self._wnd, + xpoints.arr, ypoints.arr, + xdirs.arr, ydirs.arr, + c_pointer(_cell))) + else: + safe_call(backend.get().af_draw_vector_field_2d(self._wnd, + xpoints.arr, ypoints.arr, zpoints.arr, + xdirs.arr, ydirs.arr, zdirs.arr, + c_pointer(_cell))) + else: + safe_call(backend.get().af_draw_plot_nd(self._wnd, points.arr, dirs.arr, c_pointer(_cell))) def surface(self, x_vals, y_vals, z_vals, title=None): """ Renders the input array as a 3D surface plot. - Paramters - --------- + Parameters + ---------- x_vals: af.Array. A 1 dimensional array containing X co-ordinates. @@ -197,14 +382,14 @@ def surface(self, x_vals, y_vals, z_vals, title=None): _cell = _Cell(self._r, self._c, title, self._cmap) safe_call(backend.get().af_draw_surface(self._wnd, x_vals.arr, y_vals.arr, z_vals.arr, - ct.pointer(_cell))) + c_pointer(_cell))) def hist(self, X, min_val, max_val, title=None): """ Display a histogram Plot. - Paramters - --------- + Parameters + ---------- X: af.Array. A 1 dimensional array containing the histogram. @@ -220,8 +405,8 @@ def hist(self, X, min_val, max_val, title=None): """ _cell = _Cell(self._r, self._c, title, self._cmap) safe_call(backend.get().af_draw_hist(self._wnd, X.arr, - ct.c_double(max_val), ct.c_double(min_val), - ct.pointer(_cell))) + c_double_t(max_val), c_double_t(min_val), + c_pointer(_cell))) def grid(self, rows, cols): """ @@ -237,7 +422,7 @@ def grid(self, rows, cols): Number of columns in the grid. """ - safe_call(backend.get().af_grid(self._wnd, ct.c_int(rows), ct.c_int(cols))) + safe_call(backend.get().af_grid(self._wnd, c_int_t(rows), c_int_t(cols))) def show(self): """ @@ -251,10 +436,94 @@ def close(self): """ Close the window. """ - tmp = ct.c_bool(True) - safe_call(backend.get().af_is_window_closed(ct.pointer(tmp), self._wnd)) + tmp = c_bool_t(True) + safe_call(backend.get().af_is_window_closed(c_pointer(tmp), self._wnd)) return tmp + def set_visibility(is_visible): + """ + A flag that shows or hides the window as requested. + + Parameters + ---------- + is_visible: Flag specifying the visibility of the flag. + """ + safe_call(backend.get().af_set_visibility(self._wnd, is_visible)) + + def set_axes_limits(self, xmin, xmax, ymin, ymax, zmin=None, zmax=None, exact=False): + """ + Set axis limits. + + Parameters + ---------- + + xmin : af.Array. + - lower limit of the x axis. + + xmax : af.Array. + - upper limit of the x axis. + + ymin : af.Array. + - lower limit of the y axis. + + ymax : af.Array. + - upper limit of the y axis. + + zmin : optional: af.Array. default: None. + - lower limit of the z axis. + + zmax : optional: af.Array. default: None. + - upper limit of the z axis. + + title : str. + Title used for the plot. + + Note + ---- + + The line parameter takes precedence. + """ + _cell = _Cell(self._r, self._c, "", self._cmap) + if (zmin is None or zmax is None): + safe_call(backend.get().af_set_axes_limits_2d(self._wnd, + c_float_t(xmin), c_float_t(xmax), + c_float_t(ymin), c_float_t(ymax), + exact, c_pointer(_cell))) + else: + safe_call(backend.get().af_set_axes_limits_2d(self._wnd, + c_float_t(xmin), c_float_t(xmax), + c_float_t(ymin), c_float_t(ymax), + c_float_t(zmin), c_float_t(zmax), + exact, c_pointer(_cell))) + + def set_axes_label_format(self, xformat="4.1%f", yformat="4.1%f", zformat="4.1%f"): + """ + Set axis limits. + + Parameters + ---------- + + xformat : str. + default: "4.1%f". + is a printf-style format specifier for x-axis + yformat : str. + default: "4.1%f". + is a printf-style format specifier for y-axis + zformat : str. + default: "4.1%f". + is a printf-style format specifier for z-axis + + """ + _cell = _Cell(self._r, self._c, None, self._cmap) + xformat = xformat.encode("ascii") + yformat = yformat.encode("ascii") + zformat = zformat.encode("ascii") + safe_call(backend.get().af_set_axes_label_format(self._wnd, + c_char_ptr_t(xformat), + c_char_ptr_t(yformat), + c_char_ptr_t(zformat), + c_pointer(_cell))) + def __getitem__(self, keys): """ Get access to a specific grid location within the window. diff --git a/arrayfire/image.py b/arrayfire/image.py index 49cc76d4e..f626a4802 100644 --- a/arrayfire/image.py +++ b/arrayfire/image.py @@ -8,12 +8,13 @@ ######################################################## """ -Image processing functions for arrayfire. +Image processing functions. """ from .library import * from .array import * from .data import constant +from .signal import medfilt import os def gradient(image): @@ -35,7 +36,7 @@ def gradient(image): """ dx = Array() dy = Array() - safe_call(backend.get().af_gradient(ct.pointer(dx.arr), ct.pointer(dy.arr), image.arr)) + safe_call(backend.get().af_gradient(c_pointer(dx.arr), c_pointer(dy.arr), image.arr)) return dx, dy def load_image(file_name, is_color=False): @@ -58,8 +59,8 @@ def load_image(file_name, is_color=False): """ assert(os.path.isfile(file_name)) image = Array() - safe_call(backend.get().af_load_image(ct.pointer(image.arr), - ct.c_char_p(file_name.encode('ascii')), is_color)) + safe_call(backend.get().af_load_image(c_pointer(image.arr), + c_char_ptr_t(file_name.encode('ascii')), is_color)) return image def save_image(image, file_name): @@ -75,7 +76,7 @@ def save_image(image, file_name): - Full path of the file name on the disk. """ assert(isinstance(file_name, str)) - safe_call(backend.get().af_save_image(ct.c_char_p(file_name.encode('ascii')), image.arr)) + safe_call(backend.get().af_save_image(c_char_ptr_t(file_name.encode('ascii')), image.arr)) return image @@ -96,8 +97,8 @@ def load_image_native(file_name): """ assert(os.path.isfile(file_name)) image = Array() - safe_call(backend.get().af_load_image_native(ct.pointer(image.arr), - ct.c_char_p(file_name.encode('ascii')))) + safe_call(backend.get().af_load_image_native(c_pointer(image.arr), + c_char_ptr_t(file_name.encode('ascii')))) return image def save_image_native(image, file_name): @@ -113,7 +114,7 @@ def save_image_native(image, file_name): - Full path of the file name on the disk. """ assert(isinstance(file_name, str)) - safe_call(backend.get().af_save_image_native(ct.c_char_p(file_name.encode('ascii')), image.arr)) + safe_call(backend.get().af_save_image_native(c_char_ptr_t(file_name.encode('ascii')), image.arr)) return image def resize(image, scale=None, odim0=None, odim1=None, method=INTERP.NEAREST): @@ -159,9 +160,9 @@ def resize(image, scale=None, odim0=None, odim1=None, method=INTERP.NEAREST): odim1 = int(scale * idims[1]) output = Array() - safe_call(backend.get().af_resize(ct.pointer(output.arr), - image.arr, ct.c_longlong(odim0), - ct.c_longlong(odim1), method.value)) + safe_call(backend.get().af_resize(c_pointer(output.arr), + image.arr, c_dim_t(odim0), + c_dim_t(odim1), method.value)) return output @@ -202,12 +203,13 @@ def transform(image, trans_mat, odim0 = 0, odim1 = 0, method=INTERP.NEAREST, is_ """ output = Array() - safe_call(backend.get().af_transform(ct.pointer(output.arr), + safe_call(backend.get().af_transform(c_pointer(output.arr), image.arr, trans_mat.arr, - ct.c_longlong(odim0), ct.c_longlong(odim1), + c_dim_t(odim0), c_dim_t(odim1), method.value, is_inverse)) return output + def rotate(image, theta, is_crop = True, method = INTERP.NEAREST): """ Rotate an image. @@ -233,8 +235,8 @@ def rotate(image, theta, is_crop = True, method = INTERP.NEAREST): - Output image after rotating. """ output = Array() - safe_call(backend.get().af_rotate(ct.pointer(output.arr), image.arr, - ct.c_double(theta), is_crop, method.value)) + safe_call(backend.get().af_rotate(c_pointer(output.arr), image.arr, + c_float_t(theta), is_crop, method.value)) return output def translate(image, trans0, trans1, odim0 = 0, odim1 = 0, method = INTERP.NEAREST): @@ -274,9 +276,9 @@ def translate(image, trans0, trans1, odim0 = 0, odim1 = 0, method = INTERP.NEARE """ output = Array() - safe_call(backend.get().af_translate(ct.pointer(output.arr), + safe_call(backend.get().af_translate(c_pointer(output.arr), image.arr, trans0, trans1, - ct.c_longlong(odim0), ct.c_longlong(odim1), method.value)) + c_dim_t(odim0), c_dim_t(odim1), method.value)) return output def scale(image, scale0, scale1, odim0 = 0, odim1 = 0, method = INTERP.NEAREST): @@ -316,9 +318,9 @@ def scale(image, scale0, scale1, odim0 = 0, odim1 = 0, method = INTERP.NEAREST): """ output = Array() - safe_call(backend.get().af_scale(ct.pointer(output.arr), - image.arr, ct.c_double(scale0), ct.c_double(scale1), - ct.c_longlong(odim0), ct.c_longlong(odim1), method.value)) + safe_call(backend.get().af_scale(c_pointer(output.arr), + image.arr, c_float_t(scale0), c_float_t(scale1), + c_dim_t(odim0), c_dim_t(odim1), method.value)) return output def skew(image, skew0, skew1, odim0 = 0, odim1 = 0, method = INTERP.NEAREST, is_inverse=True): @@ -361,9 +363,9 @@ def skew(image, skew0, skew1, odim0 = 0, odim1 = 0, method = INTERP.NEAREST, is_ """ output = Array() - safe_call(backend.get().af_skew(ct.pointer(output.arr), - image.arr, ct.c_double(skew0), ct.c_double(skew1), - ct.c_longlong(odim0), ct.c_longlong(odim1), + safe_call(backend.get().af_skew(c_pointer(output.arr), + image.arr, c_float_t(skew0), c_float_t(skew1), + c_dim_t(odim0), c_dim_t(odim1), method.value, is_inverse)) return output @@ -405,9 +407,9 @@ def histogram(image, nbins, min_val = None, max_val = None): max_val = af_max(image) output = Array() - safe_call(backend.get().af_histogram(ct.pointer(output.arr), - image.arr, ct.c_uint(nbins), - ct.c_double(min_val), ct.c_double(max_val))) + safe_call(backend.get().af_histogram(c_pointer(output.arr), + image.arr, c_uint_t(nbins), + c_double_t(min_val), c_double_t(max_val))) return output def hist_equal(image, hist): @@ -431,7 +433,7 @@ def hist_equal(image, hist): """ output = Array() - safe_call(backend.get().af_hist_equal(ct.pointer(output.arr), image.arr, hist.arr)) + safe_call(backend.get().af_hist_equal(c_pointer(output.arr), image.arr, hist.arr)) return output def dilate(image, mask = None): @@ -459,7 +461,7 @@ def dilate(image, mask = None): mask = constant(1, 3, 3, dtype=Dtype.f32) output = Array() - safe_call(backend.get().af_dilate(ct.pointer(output.arr), image.arr, mask.arr)) + safe_call(backend.get().af_dilate(c_pointer(output.arr), image.arr, mask.arr)) return output @@ -488,7 +490,7 @@ def dilate3(volume, mask = None): mask = constant(1, 3, 3, 3, dtype=Dtype.f32) output = Array() - safe_call(backend.get().af_dilate3(ct.pointer(output.arr), volume.arr, mask.arr)) + safe_call(backend.get().af_dilate3(c_pointer(output.arr), volume.arr, mask.arr)) return output @@ -517,7 +519,7 @@ def erode(image, mask = None): mask = constant(1, 3, 3, dtype=Dtype.f32) output = Array() - safe_call(backend.get().af_erode(ct.pointer(output.arr), image.arr, mask.arr)) + safe_call(backend.get().af_erode(c_pointer(output.arr), image.arr, mask.arr)) return output @@ -547,7 +549,7 @@ def erode3(volume, mask = None): mask = constant(1, 3, 3, 3, dtype=Dtype.f32) output = Array() - safe_call(backend.get().af_erode3(ct.pointer(output.arr), volume.arr, mask.arr)) + safe_call(backend.get().af_erode3(c_pointer(output.arr), volume.arr, mask.arr)) return output @@ -578,9 +580,9 @@ def bilateral(image, s_sigma, c_sigma, is_color = False): """ output = Array() - safe_call(backend.get().af_bilateral(ct.pointer(output.arr), - image.arr, ct.c_double(s_sigma), - ct.c_double(c_sigma), is_color)) + safe_call(backend.get().af_bilateral(c_pointer(output.arr), + image.arr, c_float_t(s_sigma), + c_float_t(c_sigma), is_color)) return output def mean_shift(image, s_sigma, c_sigma, n_iter, is_color = False): @@ -613,14 +615,14 @@ def mean_shift(image, s_sigma, c_sigma, n_iter, is_color = False): """ output = Array() - safe_call(backend.get().af_mean_shift(ct.pointer(output.arr), - image.arr, ct.c_double(s_sigma), ct.c_double(c_sigma), - ct.c_uint(n_iter), is_color)) + safe_call(backend.get().af_mean_shift(c_pointer(output.arr), + image.arr, c_float_t(s_sigma), c_float_t(c_sigma), + c_uint_t(n_iter), is_color)) return output -def medfilt(image, w0 = 3, w1 = 3, edge_pad = PAD.ZERO): +def minfilt(image, w_len = 3, w_wid = 3, edge_pad = PAD.ZERO): """ - Apply median filter for the image. + Apply min filter for the image. Parameters ---------- @@ -635,24 +637,24 @@ def medfilt(image, w0 = 3, w1 = 3, edge_pad = PAD.ZERO): - The length of the filter along the second dimension. edge_pad : optional: af.PAD. default: af.PAD.ZERO - - Flag specifying how the median at the edge should be treated. + - Flag specifying how the min at the edge should be treated. Returns --------- output : af.Array - - The image after median filter is applied. + - The image after min filter is applied. """ output = Array() - safe_call(backend.get().af_medfilt(ct.pointer(output.arr), - image.arr, ct.c_longlong(w0), - ct.c_longlong(w1), edge_pad.value)) + safe_call(backend.get().af_minfilt(c_pointer(output.arr), + image.arr, c_dim_t(w_len), + c_dim_t(w_wid), edge_pad.value)) return output -def minfilt(image, w_len = 3, w_wid = 3, edge_pad = PAD.ZERO): +def maxfilt(image, w_len = 3, w_wid = 3, edge_pad = PAD.ZERO): """ - Apply min filter for the image. + Apply max filter for the image. Parameters ---------- @@ -667,78 +669,89 @@ def minfilt(image, w_len = 3, w_wid = 3, edge_pad = PAD.ZERO): - The length of the filter along the second dimension. edge_pad : optional: af.PAD. default: af.PAD.ZERO - - Flag specifying how the min at the edge should be treated. + - Flag specifying how the max at the edge should be treated. Returns --------- output : af.Array - - The image after min filter is applied. + - The image after max filter is applied. """ output = Array() - safe_call(backend.get().af_minfilt(ct.pointer(output.arr), - image.arr, ct.c_longlong(w_len), - ct.c_longlong(w_wid), edge_pad.value)) + safe_call(backend.get().af_maxfilt(c_pointer(output.arr), + image.arr, c_dim_t(w_len), + c_dim_t(w_wid), edge_pad.value)) return output -def maxfilt(image, w_len = 3, w_wid = 3, edge_pad = PAD.ZERO): +def regions(image, conn = CONNECTIVITY.FOUR, out_type = Dtype.f32): """ - Apply max filter for the image. + Find the connected components in the image. Parameters ---------- image : af.Array - - A 2 D arrayfire array representing an image, or - - A multi dimensional array representing batch of images. - - w0 : optional: int. default: 3. - - The length of the filter along the first dimension. + - A 2 D arrayfire array representing an image. - w1 : optional: int. default: 3. - - The length of the filter along the second dimension. + conn : optional: af.CONNECTIVITY. default: af.CONNECTIVITY.FOUR. + - Specifies the connectivity of the pixels. - edge_pad : optional: af.PAD. default: af.PAD.ZERO - - Flag specifying how the max at the edge should be treated. + out_type : optional: af.Dtype. default: af.Dtype.f32. + - Specifies the type for the output. Returns --------- output : af.Array - - The image after max filter is applied. + - An array where each pixel is labeled with its component number. """ output = Array() - safe_call(backend.get().af_maxfilt(ct.pointer(output.arr), - image.arr, ct.c_longlong(w_len), - ct.c_longlong(w_wid), edge_pad.value)) + safe_call(backend.get().af_regions(c_pointer(output.arr), image.arr, + conn.value, out_type.value)) return output -def regions(image, conn = CONNECTIVITY.FOUR, out_type = Dtype.f32): +def confidenceCC(image, seedx, seedy, radius, multiplier, iters, segmented_value): """ - Find the connected components in the image. + Find the confidence connected components in the image. Parameters ---------- image : af.Array - A 2 D arrayfire array representing an image. + Expects non-integral type - conn : optional: af.CONNECTIVITY. default: af.CONNECTIVITY.FOUR. - - Specifies the connectivity of the pixels. + seedx : af.Array + - An array with x-coordinates of seed points - out_type : optional: af.Dtype. default: af.Dtype.f32. - - Specifies the type for the output. + seedy : af.Array + - An array with y-coordinates of seed points + + radius : scalar + - The neighborhood region to be considered around + each seed point + + multiplier : scalar + - Controls the threshold range computed from + the mean and variance of seed point neighborhoods + + iters : scalar + - is number of iterations + + segmented_value : scalar + - the value to which output array valid + pixels are set to. Returns --------- output : af.Array - - An array where each pixel is labeled with its component number. + - Output array with resulting connected components """ output = Array() - safe_call(backend.get().af_regions(ct.pointer(output.arr), image.arr, - conn.value, out_type.value)) + safe_call(backend.get().af_confidence_cc(c_pointer(output.arr), image.arr, seedx.arr, seedy.arr, + c_uint_t(radius), c_uint_t(multiplier), c_int_t(iters), c_double_t(segmented_value))) return output def sobel_derivatives(image, w_len=3): @@ -764,10 +777,52 @@ def sobel_derivatives(image, w_len=3): """ dx = Array() dy = Array() - safe_call(backend.get().af_sobel_operator(ct.pointer(dx.arr), ct.pointer(dy.arr), - image.arr, ct.c_uint(w_len))) + safe_call(backend.get().af_sobel_operator(c_pointer(dx.arr), c_pointer(dy.arr), + image.arr, c_uint_t(w_len))) return dx,dy +def gaussian_kernel(rows, cols, sigma_r = None, sigma_c = None): + """ + Create a gaussian kernel with the given parameters. + + Parameters + ---------- + image : af.Array + - A 2 D arrayfire array representing an image, or + - A multi dimensional array representing batch of images. + + rows : int + - The number of rows in the gaussian kernel. + + cols : int + - The number of columns in the gaussian kernel. + + sigma_r : optional: number. default: None. + - The sigma value along rows + - If None, calculated as (0.25 * rows + 0.75) + + sigma_c : optional: number. default: None. + - The sigma value along columns + - If None, calculated as (0.25 * cols + 0.75) + + Returns + ------- + out : af.Array + - A gaussian kernel of size (rows, cols) + """ + out = Array() + + if (sigma_r is None): + sigma_r = 0.25 * rows + 0.75 + + if (sigma_c is None): + sigma_c = 0.25 * cols + 0.75 + + safe_call(backend.get().af_gaussian_kernel(c_pointer(out.arr), + c_int_t(rows), c_int_t(cols), + c_double_t(sigma_r), c_double_t(sigma_c))) + return out + def sobel_filter(image, w_len = 3, is_fast = False): """ Apply sobel filter to the image. @@ -827,8 +882,8 @@ def rgb2gray(image, r_factor = 0.2126, g_factor = 0.7152, b_factor = 0.0722): """ output=Array() - safe_call(backend.get().af_rgb2gray(ct.pointer(output.arr), - image.arr, ct.c_float(r_factor), ct.c_float(g_factor), ct.c_float(b_factor))) + safe_call(backend.get().af_rgb2gray(c_pointer(output.arr), + image.arr, c_float_t(r_factor), c_float_t(g_factor), c_float_t(b_factor))) return output def gray2rgb(image, r_factor = 1.0, g_factor = 1.0, b_factor = 1.0): @@ -859,8 +914,8 @@ def gray2rgb(image, r_factor = 1.0, g_factor = 1.0, b_factor = 1.0): """ output=Array() - safe_call(backend.get().af_gray2rgb(ct.pointer(output.arr), - image.arr, ct.c_float(r_factor), ct.c_float(g_factor), ct.c_float(b_factor))) + safe_call(backend.get().af_gray2rgb(c_pointer(output.arr), + image.arr, c_float_t(r_factor), c_float_t(g_factor), c_float_t(b_factor))) return output def hsv2rgb(image): @@ -881,7 +936,7 @@ def hsv2rgb(image): """ output = Array() - safe_call(backend.get().af_hsv2rgb(ct.pointer(output.arr), image.arr)) + safe_call(backend.get().af_hsv2rgb(c_pointer(output.arr), image.arr)) return output def rgb2hsv(image): @@ -902,7 +957,7 @@ def rgb2hsv(image): """ output = Array() - safe_call(backend.get().af_rgb2hsv(ct.pointer(output.arr), image.arr)) + safe_call(backend.get().af_rgb2hsv(c_pointer(output.arr), image.arr)) return output def color_space(image, to_type, from_type): @@ -928,7 +983,7 @@ def color_space(image, to_type, from_type): """ output = Array() - safe_call(backend.get().af_color_space(ct.pointer(output.arr), image.arr, + safe_call(backend.get().af_color_space(c_pointer(output.arr), image.arr, to_type.value, from_type.value)) return output @@ -994,10 +1049,10 @@ def unwrap(image, wx, wy, sx, sy, px=0, py=0, is_column=True): """ out = Array() - safe_call(backend.get().af_unwrap(ct.pointer(out.arr), image.arr, - ct.c_longlong(wx), ct.c_longlong(wy), - ct.c_longlong(sx), ct.c_longlong(sy), - ct.c_longlong(px), ct.c_longlong(py), + safe_call(backend.get().af_unwrap(c_pointer(out.arr), image.arr, + c_dim_t(wx), c_dim_t(wy), + c_dim_t(sx), c_dim_t(sy), + c_dim_t(px), c_dim_t(py), is_column)) return out @@ -1076,11 +1131,11 @@ def wrap(a, ox, oy, wx, wy, sx, sy, px=0, py=0, is_column=True): """ out = Array() - safe_call(backend.get().af_wrap(ct.pointer(out.arr), a.arr, - ct.c_longlong(ox), ct.c_longlong(oy), - ct.c_longlong(wx), ct.c_longlong(wy), - ct.c_longlong(sx), ct.c_longlong(sy), - ct.c_longlong(px), ct.c_longlong(py), + safe_call(backend.get().af_wrap(c_pointer(out.arr), a.arr, + c_dim_t(ox), c_dim_t(oy), + c_dim_t(wx), c_dim_t(wy), + c_dim_t(sx), c_dim_t(sy), + c_dim_t(px), c_dim_t(py), is_column)) return out @@ -1100,7 +1155,7 @@ def sat(image): """ out = Array() - safe_call(backend.get().af_sat(ct.pointer(out.arr), image.arr)) + safe_call(backend.get().af_sat(c_pointer(out.arr), image.arr)) return out def ycbcr2rgb(image, standard=YCC_STD.BT_601): @@ -1126,7 +1181,7 @@ def ycbcr2rgb(image, standard=YCC_STD.BT_601): """ out = Array() - safe_call(backend.get().af_ycbcr2rgb(ct.pointer(out.arr), image.arr, standard.value)) + safe_call(backend.get().af_ycbcr2rgb(c_pointer(out.arr), image.arr, standard.value)) return out def rgb2ycbcr(image, standard=YCC_STD.BT_601): @@ -1152,5 +1207,199 @@ def rgb2ycbcr(image, standard=YCC_STD.BT_601): """ out = Array() - safe_call(backend.get().af_rgb2ycbcr(ct.pointer(out.arr), image.arr, standard.value)) + safe_call(backend.get().af_rgb2ycbcr(c_pointer(out.arr), image.arr, standard.value)) + return out + +def moments(image, moment = MOMENT.FIRST_ORDER): + """ + Calculate image moments. + + Parameters + ---------- + image : af.Array + - A 2 D arrayfire array representing an image, or + - A multi dimensional array representing batch of images. + + moment : optional: af.MOMENT. default: af.MOMENT.FIRST_ORDER. + Moment(s) to calculate. Can be one of: + - af.MOMENT.M00 + - af.MOMENT.M01 + - af.MOMENT.M10 + - af.MOMENT.M11 + - af.MOMENT.FIRST_ORDER + + Returns + --------- + out : af.Array + - array containing requested moment(s) of each image + """ + output = Array() + safe_call(backend.get().af_moments(c_pointer(output.arr), image.arr, moment.value)) + return output + +def canny(image, + low_threshold, high_threshold = None, + threshold_type = CANNY_THRESHOLD.MANUAL, + sobel_window = 3, is_fast = False): + """ + Canny edge detector. + + Parameters + ---------- + image : af.Array + - A 2 D arrayfire array representing an image + + threshold_type : optional: af.CANNY_THRESHOLD. default: af.CANNY_THRESHOLD.MANUAL. + Can be one of: + - af.CANNY_THRESHOLD.MANUAL + - af.CANNY_THRESHOLD.AUTO_OTSU + + low_threshold : required: float. + Specifies the % of maximum in gradient image if threshold_type is MANUAL. + Specifies the % of auto dervied high value if threshold_type is AUTO_OTSU. + + high_threshold : optional: float. default: None + Specifies the % of maximum in gradient image if threshold_type is MANUAL. + Ignored if threshold_type is AUTO_OTSU + + sobel_window : optional: int. default: 3 + Specifies the size of sobel kernel when computing the gradient image. + + Returns + -------- + + out : af.Array + - A binary image containing the edges + + """ + output = Array() + if threshold_type.value == CANNY_THRESHOLD.MANUAL.value: + assert(high_threshold is not None) + + high_threshold = high_threshold if high_threshold else 0 + safe_call(backend.get().af_canny(c_pointer(output.arr), image.arr, + c_int_t(threshold_type.value), + c_float_t(low_threshold), c_float_t(high_threshold), + c_uint_t(sobel_window), c_bool_t(is_fast))) + return output + +def anisotropic_diffusion(image, time_step, conductance, iterations, flux_function_type = FLUX.QUADRATIC, diffusion_kind = DIFFUSION.GRAD): + """ + Anisotropic smoothing filter. + + Parameters + ---------- + image: af.Array + The input image. + + time_step: scalar. + The time step used in solving the diffusion equation. + + conductance: + Controls conductance sensitivity in diffusion equation. + + iterations: + Number of times the diffusion step is performed. + + flux_function_type: + Type of flux function to be used. Available flux functions: + - Quadratic (af.FLUX.QUADRATIC) + - Exponential (af.FLUX.EXPONENTIAL) + + diffusion_kind: + Type of diffusion equatoin to be used. Available diffusion equations: + - Gradient diffusion equation (af.DIFFUSION.GRAD) + - Modified curvature diffusion equation (af.DIFFUSION.MCDE) + + Returns + ------- + out: af.Array + Anisotropically-smoothed output image. + + """ + out = Array() + safe_call(backend.get(). + af_anisotropic_diffusion(c_pointer(out.arr), image.arr, + c_float_t(time_step), c_float_t(conductance), c_uint_t(iterations), + flux_function_type.value, diffusion_kind.value)) + return out + +def iterativeDeconv(image, psf, iterations, relax_factor, algo = ITERATIVE_DECONV.DEFAULT): + """ + Iterative deconvolution algorithm. + + Parameters + ---------- + image: af.Array + The blurred input image. + + psf: af.Array + The kernel(point spread function) known to have caused + the blur in the system. + + iterations: + Number of times the algorithm will run. + + relax_factor: scalar. + is the relaxation factor multiplied with distance + of estimate from observed image. + + algo: + takes enum value of type af.ITERATIVE_DECONV + indicating the iterative deconvolution algorithm to be used + + Returns + ------- + out: af.Array + sharp image estimate generated from the blurred input + + Note + ------- + relax_factor argument is ignored when the RICHARDSONLUCY algorithm is used. + + """ + out = Array() + safe_call(backend.get(). + af_iterative_deconv(c_pointer(out.arr), image.arr, psf.arr, + c_uint_t(iterations), c_float_t(relax_factor), algo.value)) return out + +def inverseDeconv(image, psf, gamma, algo = ITERATIVE_DECONV.DEFAULT): + """ + Inverse deconvolution algorithm. + + Parameters + ---------- + image: af.Array + The blurred input image. + + psf: af.Array + The kernel(point spread function) known to have caused + the blur in the system. + + gamma: scalar. + is a user defined regularization constant + + algo: + takes enum value of type af.INVERSE_DECONV + indicating the inverse deconvolution algorithm to be used + + Returns + ------- + out: af.Array + sharp image estimate generated from the blurred input + + """ + out = Array() + safe_call(backend.get(). + af_inverse_deconv(c_pointer(out.arr), image.arr, psf.arr, + c_float_t(gamma), algo.value)) + return out + +def is_image_io_available(): + """ + Function to check if the arrayfire library was built with Image IO support. + """ + res = c_bool_t(False) + safe_call(backend.get().af_is_image_io_available(c_pointer(res))) + return res.value diff --git a/arrayfire/index.py b/arrayfire/index.py index baad324c0..093a88336 100644 --- a/arrayfire/index.py +++ b/arrayfire/index.py @@ -7,7 +7,7 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## """ -classes required for indexing operations +Index and Seq classes used in indexing operations. """ from .library import * @@ -39,27 +39,40 @@ class Seq(ct.Structure): S: slice or number. """ - _fields_ = [("begin", ct.c_double), - ("end" , ct.c_double), - ("step" , ct.c_double)] + _fields_ = [("begin", c_double_t), + ("end" , c_double_t), + ("step" , c_double_t)] def __init__ (self, S): - self.begin = ct.c_double( 0) - self.end = ct.c_double(-1) - self.step = ct.c_double( 1) + self.begin = c_double_t( 0) + self.end = c_double_t(-1) + self.step = c_double_t( 1) if _is_number(S): - self.begin = ct.c_double(S) - self.end = ct.c_double(S) + self.begin = c_double_t(S) + self.end = c_double_t(S) elif isinstance(S, slice): if (S.step is not None): - self.step = ct.c_double(S.step) + self.step = c_double_t(S.step) if(S.step < 0): self.begin, self.end = self.end, self.begin if (S.start is not None): - self.begin = ct.c_double(S.start) + self.begin = c_double_t(S.start) if (S.stop is not None): - self.end = ct.c_double(S.stop - math.copysign(1, self.step)) + self.end = c_double_t(S.stop) + + # handle special cases + if self.begin >= 0 and self.end >=0 and self.end <= self.begin and self.step >= 0: + self.begin = 1 + self.end = 1 + self.step = 1 + elif self.begin < 0 and self.end < 0 and self.end >= self.begin and self.step <= 0: + self.begin = -2 + self.end = -2 + self.step = -1 + + if (S.stop is not None): + self.end = self.end - math.copysign(1, self.step) else: raise IndexError("Invalid type while indexing arrayfire.array") @@ -146,13 +159,13 @@ def __next__(self): return self.next() class _uidx(ct.Union): - _fields_ = [("arr", ct.c_void_p), + _fields_ = [("arr", c_void_ptr_t), ("seq", Seq)] class Index(ct.Structure): _fields_ = [("idx", _uidx), - ("isSeq", ct.c_bool), - ("isBatch", ct.c_bool)] + ("isSeq", c_bool_t), + ("isBatch", c_bool_t)] """ Container for the index class in arrayfire C library @@ -194,12 +207,12 @@ def __init__ (self, idx): if isinstance(idx, BaseArray): - arr = ct.c_void_p(0) + arr = c_void_ptr_t(0) if (idx.type() == Dtype.b8.value): - safe_call(backend.get().af_where(ct.pointer(arr), idx.arr)) + safe_call(backend.get().af_where(c_pointer(arr), idx.arr)) else: - safe_call(backend.get().af_retain_array(ct.pointer(arr), idx.arr)) + safe_call(backend.get().af_retain_array(c_pointer(arr), idx.arr)) self.idx.arr = arr self.isSeq = False @@ -214,20 +227,21 @@ def __del__(self): # ctypes field variables are automatically # converted to basic C types so we have to # build the void_p from the value again. - arr = ct.c_void_p(self.idx.arr) + arr = c_void_ptr_t(self.idx.arr) backend.get().af_release_array(arr) +_span = Index(slice(None)) class _Index4(object): - def __init__(self, idx0, idx1, idx2, idx3): + def __init__(self): index_vec = Index * 4 - self.array = index_vec(idx0, idx1, idx2, idx3) + self.array = index_vec(_span, _span, _span, _span) # Do not lose those idx as self.array keeps # no reference to them. Otherwise the destructor # is prematurely called - self.idxs = [idx0,idx1,idx2,idx3] + self.idxs = [_span, _span, _span, _span] @property def pointer(self): - return ct.pointer(self.array) + return c_pointer(self.array) def __getitem__(self, idx): return self.array[idx] diff --git a/arrayfire/interop.py b/arrayfire/interop.py index 809562927..f2b9a1f68 100644 --- a/arrayfire/interop.py +++ b/arrayfire/interop.py @@ -10,20 +10,67 @@ """ Interop with other python packages. -This module provides interoperability with the following python packages. +This module provides helper functions to copy data to arrayfire from the following modules: + + 1. numpy - numpy.ndarray + 2. pycuda - pycuda.gpuarray + 3. pyopencl - pyopencl.array + 4. numba - numba.cuda.cudadrv.devicearray.DeviceNDArray - 1. numpy """ from .array import * +from .device import * + + +def _fc_to_af_array(in_ptr, in_shape, in_dtype, is_device=False, copy = True): + """ + Fortran Contiguous to af array + """ + res = Array(in_ptr, in_shape, in_dtype, is_device=is_device) + + if not is_device: + return res + + lock_array(res) + return res.copy() if copy else res + +def _cc_to_af_array(in_ptr, ndim, in_shape, in_dtype, is_device=False, copy = True): + """ + C Contiguous to af array + """ + if ndim == 1: + return _fc_to_af_array(in_ptr, in_shape, in_dtype, is_device, copy) + else: + shape = tuple(reversed(in_shape)) + res = Array(in_ptr, shape, in_dtype, is_device=is_device) + if is_device: lock_array(res) + return res._reorder() + +_nptype_to_aftype = {'b1' : Dtype.b8, + 'u1' : Dtype.u8, + 'u2' : Dtype.u16, + 'i2' : Dtype.s16, + 's4' : Dtype.u32, + 'i4' : Dtype.s32, + 'f4' : Dtype.f32, + 'c8' : Dtype.c32, + 's8' : Dtype.u64, + 'i8' : Dtype.s64, + 'f8' : Dtype.f64, + 'c16' : Dtype.c64} try: import numpy as np +except ImportError: + AF_NUMPY_FOUND=False +else: + from numpy import ndarray as NumpyArray from .data import reorder AF_NUMPY_FOUND=True - def np_to_af_array(np_arr): + def np_to_af_array(np_arr, copy=True): """ Convert numpy.ndarray to arrayfire.Array. @@ -31,32 +78,216 @@ def np_to_af_array(np_arr): ---------- np_arr : numpy.ndarray() + copy : Bool specifying if array is to be copied. + Default is true. + Can only be False if array is fortran contiguous. + Returns --------- af_arr : arrayfire.Array() """ + + in_shape = np_arr.shape + in_ptr = np_arr.ctypes.data_as(c_void_ptr_t) + in_dtype = _nptype_to_aftype[np_arr.dtype.str[1:]] + + if not copy: + raise RuntimeError("Copy can not be False for numpy arrays") + if (np_arr.flags['F_CONTIGUOUS']): - return Array(np_arr.ctypes.data, np_arr.shape, np_arr.dtype.char) + return _fc_to_af_array(in_ptr, in_shape, in_dtype) elif (np_arr.flags['C_CONTIGUOUS']): - if np_arr.ndim == 1: - return Array(np_arr.ctypes.data, np_arr.shape, np_arr.dtype.char) - elif np_arr.ndim == 2: - shape = (np_arr.shape[1], np_arr.shape[0]) - res = Array(np_arr.ctypes.data, shape, np_arr.dtype.char) - return reorder(res, 1, 0) - elif np_arr.ndim == 3: - shape = (np_arr.shape[2], np_arr.shape[1], np_arr.shape[0]) - res = Array(np_arr.ctypes.data, shape, np_arr.dtype.char) - return reorder(res, 2, 1, 0) - elif np_arr.ndim == 4: - shape = (np_arr.shape[3], np_arr.shape[2], np_arr.shape[1], np_arr.shape[0]) - res = Array(np_arr.ctypes.data, shape, np_arr.dtype.char) - return reorder(res, 3, 2, 1, 0) - else: - raise RuntimeError("Unsupported ndim") + return _cc_to_af_array(in_ptr, np_arr.ndim, in_shape, in_dtype) else: - return np_to_af_array(np.asfortranarray(np_arr)) + return np_to_af_array(np_arr.copy()) from_ndarray = np_to_af_array -except: - AF_NUMPY_FOUND=False + +try: + import pycuda.gpuarray +except ImportError: + AF_PYCUDA_FOUND=False +else: + from pycuda.gpuarray import GPUArray as CudaArray + AF_PYCUDA_FOUND=True + + def pycuda_to_af_array(pycu_arr, copy=True): + """ + Convert pycuda.gpuarray to arrayfire.Array + + Parameters + ----------- + pycu_arr : pycuda.GPUArray() + + copy : Bool specifying if array is to be copied. + Default is true. + Can only be False if array is fortran contiguous. + + Returns + ---------- + af_arr : arrayfire.Array() + + Note + ---------- + The input array is copied to af.Array + """ + + in_ptr = pycu_arr.ptr + in_shape = pycu_arr.shape + in_dtype = pycu_arr.dtype.char + + if not copy and not pycu_arr.flags.f_contiguous: + raise RuntimeError("Copy can only be False when arr.flags.f_contiguous is True") + + if (pycu_arr.flags.f_contiguous): + return _fc_to_af_array(in_ptr, in_shape, in_dtype, True, copy) + elif (pycu_arr.flags.c_contiguous): + return _cc_to_af_array(in_ptr, pycu_arr.ndim, in_shape, in_dtype, True, copy) + else: + return pycuda_to_af_array(pycu_arr.copy()) + +try: + from pyopencl.array import Array as OpenclArray +except ImportError: + AF_PYOPENCL_FOUND=False +else: + from .opencl import add_device_context as _add_device_context + from .opencl import set_device_context as _set_device_context + from .opencl import get_device_id as _get_device_id + from .opencl import get_context as _get_context + AF_PYOPENCL_FOUND=True + + def pyopencl_to_af_array(pycl_arr, copy=True): + """ + Convert pyopencl.gpuarray to arrayfire.Array + + Parameters + ----------- + pycl_arr : pyopencl.Array() + + copy : Bool specifying if array is to be copied. + Default is true. + Can only be False if array is fortran contiguous. + + Returns + ---------- + af_arr : arrayfire.Array() + + Note + ---------- + The input array is copied to af.Array + """ + + ctx = pycl_arr.context.int_ptr + que = pycl_arr.queue.int_ptr + dev = pycl_arr.queue.device.int_ptr + + dev_idx = None + ctx_idx = None + for n in range(get_device_count()): + set_device(n) + dev_idx = _get_device_id() + ctx_idx = _get_context() + if (dev_idx == dev and ctx_idx == ctx): + break + + if (dev_idx == None or ctx_idx == None or + dev_idx != dev or ctx_idx != ctx): + print("Adding context and queue") + _add_device_context(dev, ctx, que) + _set_device_context(dev, ctx) + + info() + in_ptr = pycl_arr.base_data.int_ptr + in_shape = pycl_arr.shape + in_dtype = pycl_arr.dtype.char + + if not copy and not pycl_arr.flags.f_contiguous: + raise RuntimeError("Copy can only be False when arr.flags.f_contiguous is True") + + print("Copying array") + print(pycl_arr.base_data.int_ptr) + if (pycl_arr.flags.f_contiguous): + return _fc_to_af_array(in_ptr, in_shape, in_dtype, True, copy) + elif (pycl_arr.flags.c_contiguous): + return _cc_to_af_array(in_ptr, pycl_arr.ndim, in_shape, in_dtype, True, copy) + else: + return pyopencl_to_af_array(pycl_arr.copy()) + +try: + import numba +except ImportError: + AF_NUMBA_FOUND=False +else: + from numba import cuda + NumbaCudaArray = cuda.cudadrv.devicearray.DeviceNDArray + AF_NUMBA_FOUND=True + + def numba_to_af_array(nb_arr, copy=True): + """ + Convert numba.gpuarray to arrayfire.Array + + Parameters + ----------- + nb_arr : numba.cuda.cudadrv.devicearray.DeviceNDArray() + + copy : Bool specifying if array is to be copied. + Default is true. + Can only be False if array is fortran contiguous. + + Returns + ---------- + af_arr : arrayfire.Array() + + Note + ---------- + The input array is copied to af.Array + """ + + in_ptr = nb_arr.device_ctypes_pointer.value + in_shape = nb_arr.shape + in_dtype = _nptype_to_aftype[nb_arr.dtype.str[1:]] + + if not copy and not nb_arr.flags.f_contiguous: + raise RuntimeError("Copy can only be False when arr.flags.f_contiguous is True") + + if (nb_arr.is_f_contiguous()): + return _fc_to_af_array(in_ptr, in_shape, in_dtype, True, copy) + elif (nb_arr.is_c_contiguous()): + return _cc_to_af_array(in_ptr, nb_arr.ndim, in_shape, in_dtype, True, copy) + else: + return numba_to_af_array(nb_arr.copy()) + +def to_array(in_array, copy = True): + """ + Helper function to convert input from a different module to af.Array + + Parameters + ------------- + + in_array : array like object + Can be one of the following: + - numpy.ndarray + - pycuda.GPUArray + - pyopencl.Array + - numba.cuda.cudadrv.devicearray.DeviceNDArray + - array.array + - list + copy : Bool specifying if array is to be copied. + Default is true. + Can only be False if array is fortran contiguous. + + Returns + -------------- + af.Array of same dimensions as input after copying the data from the input + + """ + if AF_NUMPY_FOUND and isinstance(in_array, NumpyArray): + return np_to_af_array(in_array, copy) + if AF_PYCUDA_FOUND and isinstance(in_array, CudaArray): + return pycuda_to_af_array(in_array, copy) + if AF_PYOPENCL_FOUND and isinstance(in_array, OpenclArray): + return pyopencl_to_af_array(in_array, copy) + if AF_NUMBA_FOUND and isinstance(in_array, NumbaCudaArray): + return numba_to_af_array(in_array, copy) + return Array(src=in_array) diff --git a/arrayfire/lapack.py b/arrayfire/lapack.py index ae62f3c90..97ad92c7a 100644 --- a/arrayfire/lapack.py +++ b/arrayfire/lapack.py @@ -8,7 +8,7 @@ ######################################################## """ -Dense Linear Algebra functions for arrayfire. +Dense Linear Algebra functions (solve, inverse, etc). """ from .library import * @@ -41,7 +41,7 @@ def lu(A): L = Array() U = Array() P = Array() - safe_call(backend.get().af_lu(ct.pointer(L.arr), ct.pointer(U.arr), ct.pointer(P.arr), A.arr)) + safe_call(backend.get().af_lu(c_pointer(L.arr), c_pointer(U.arr), c_pointer(P.arr), A.arr)) return L,U,P def lu_inplace(A, pivot="lapack"): @@ -68,7 +68,7 @@ def lu_inplace(A, pivot="lapack"): """ P = Array() is_pivot_lapack = False if (pivot == "full") else True - safe_call(backend.get().af_lu_inplace(ct.pointer(P.arr), A.arr, is_pivot_lapack)) + safe_call(backend.get().af_lu_inplace(c_pointer(P.arr), A.arr, is_pivot_lapack)) return P def qr(A): @@ -98,7 +98,7 @@ def qr(A): Q = Array() R = Array() T = Array() - safe_call(backend.get().af_lu(ct.pointer(Q.arr), ct.pointer(R.arr), ct.pointer(T.arr), A.arr)) + safe_call(backend.get().af_qr(c_pointer(Q.arr), c_pointer(R.arr), c_pointer(T.arr), A.arr)) return Q,R,T def qr_inplace(A): @@ -122,7 +122,7 @@ def qr_inplace(A): This function is used to save space only when `R` is required. """ T = Array() - safe_call(backend.get().af_qr_inplace(ct.pointer(T.arr), A.arr)) + safe_call(backend.get().af_qr_inplace(c_pointer(T.arr), A.arr)) return T def cholesky(A, is_upper=True): @@ -142,6 +142,7 @@ def cholesky(A, is_upper=True): (R,info): tuple of af.Array, int. - R - triangular matrix. - info - 0 if decomposition sucessful. + Note ---- @@ -151,8 +152,8 @@ def cholesky(A, is_upper=True): """ R = Array() - info = ct.c_int(0) - safe_call(backend.get().af_cholesky(ct.pointer(R.arr), ct.pointer(info), A.arr, is_upper)) + info = c_int_t(0) + safe_call(backend.get().af_cholesky(c_pointer(R.arr), c_pointer(info), A.arr, is_upper)) return R, info.value def cholesky_inplace(A, is_upper=True): @@ -174,8 +175,8 @@ def cholesky_inplace(A, is_upper=True): 0 if decomposition sucessful. """ - info = ct.c_int(0) - safe_call(backend.get().af_cholesky_inplace(ct.pointer(info), A.arr, is_upper)) + info = c_int_t(0) + safe_call(backend.get().af_cholesky_inplace(c_pointer(info), A.arr, is_upper)) return info.value def solve(A, B, options=MATPROP.NONE): @@ -202,7 +203,7 @@ def solve(A, B, options=MATPROP.NONE): """ X = Array() - safe_call(backend.get().af_solve(ct.pointer(X.arr), A.arr, B.arr, options.value)) + safe_call(backend.get().af_solve(c_pointer(X.arr), A.arr, B.arr, options.value)) return X def solve_lu(A, P, B, options=MATPROP.NONE): @@ -230,7 +231,7 @@ def solve_lu(A, P, B, options=MATPROP.NONE): """ X = Array() - safe_call(backend.get().af_solve_lu(ct.pointer(X.arr), A.arr, P.arr, B.arr, options.value)) + safe_call(backend.get().af_solve_lu(c_pointer(X.arr), A.arr, P.arr, B.arr, options.value)) return X def inverse(A, options=MATPROP.NONE): @@ -260,7 +261,40 @@ def inverse(A, options=MATPROP.NONE): """ AI = Array() - safe_call(backend.get().af_inverse(ct.pointer(AI.arr), A.arr, options.value)) + safe_call(backend.get().af_inverse(c_pointer(AI.arr), A.arr, options.value)) + return AI + +def pinverse(A, tol=1E-6, options=MATPROP.NONE): + """ + Find pseudo-inverse(Moore-Penrose) of a matrix. + + Parameters + ---------- + + A: af.Array + - A 2 dimensional arrayfire input matrix array + + tol: optional: scalar. default: 1E-6. + - Tolerance for calculating rank + + options: optional: af.MATPROP. default: af.MATPROP.NONE. + - Currently needs to be `af.MATPROP.NONE`. + - Additional options may speed up computation in the future + + Returns + ------- + + AI: af.Array + - A 2 dimensional array that is the pseudo-inverse of `A` + + Note + ---- + + This function is not supported in GFOR + + """ + AI = Array() + safe_call(backend.get().af_pinverse(c_pointer(AI.arr), A.arr, c_double_t(tol), options.value)) return AI def rank(A, tol=1E-5): @@ -282,8 +316,8 @@ def rank(A, tol=1E-5): r: int - Rank of `A` within the given tolerance """ - r = ct.c_uint(0) - safe_call(backend.get().af_rank(ct.pointer(r), A.arr, ct.c_double(tol))) + r = c_uint_t(0) + safe_call(backend.get().af_rank(c_pointer(r), A.arr, c_double_t(tol))) return r.value def det(A): @@ -302,9 +336,9 @@ def det(A): res: scalar - Determinant of the matrix. """ - re = ct.c_double(0) - im = ct.c_double(0) - safe_call(backend.get().af_det(ct.pointer(re), ct.pointer(im), A.arr)) + re = c_double_t(0) + im = c_double_t(0) + safe_call(backend.get().af_det(c_pointer(re), c_pointer(im), A.arr)) re = re.value im = im.value return re if (im == 0) else re + im * 1j @@ -335,9 +369,9 @@ def norm(A, norm_type=NORM.EUCLID, p=1.0, q=1.0): - norm of the input """ - res = ct.c_double(0) - safe_call(backend.get().af_norm(ct.pointer(res), A.arr, norm_type.value, - ct.c_double(p), ct.c_double(q))) + res = c_double_t(0) + safe_call(backend.get().af_norm(c_pointer(res), A.arr, norm_type.value, + c_double_t(p), c_double_t(q))) return res.value def svd(A): @@ -371,7 +405,7 @@ def svd(A): U = Array() S = Array() Vt = Array() - safe_call(backend.get().af_svd(ct.pointer(U.arr), ct.pointer(S.arr), ct.pointer(Vt.arr), A.arr)) + safe_call(backend.get().af_svd(c_pointer(U.arr), c_pointer(S.arr), c_pointer(Vt.arr), A.arr)) return U, S, Vt def svd_inplace(A): @@ -405,6 +439,14 @@ def svd_inplace(A): U = Array() S = Array() Vt = Array() - safe_call(backend.get().af_svd_inplace(ct.pointer(U.arr), ct.pointer(S.arr), ct.pointer(Vt.arr), + safe_call(backend.get().af_svd_inplace(c_pointer(U.arr), c_pointer(S.arr), c_pointer(Vt.arr), A.arr)) return U, S, Vt + +def is_lapack_available(): + """ + Function to check if the arrayfire library was built with lapack support. + """ + res = c_bool_t(False) + safe_call(backend.get().af_is_lapack_available(c_pointer(res))) + return res.value diff --git a/arrayfire/library.py b/arrayfire/library.py index 4854d9334..df68f97d8 100644 --- a/arrayfire/library.py +++ b/arrayfire/library.py @@ -8,19 +8,69 @@ ######################################################## """ -module containing enums and other constants from arrayfire library +Module containing enums and other constants. """ import platform import ctypes as ct +import traceback +import os +import sys + +c_float_t = ct.c_float +c_double_t = ct.c_double +c_int_t = ct.c_int +c_uint_t = ct.c_uint +c_longlong_t = ct.c_longlong +c_ulonglong_t = ct.c_ulonglong +c_char_t = ct.c_char +c_bool_t = ct.c_bool +c_uchar_t = ct.c_ubyte +c_short_t = ct.c_short +c_ushort_t = ct.c_ushort +c_pointer = ct.pointer +c_void_ptr_t = ct.c_void_p +c_char_ptr_t = ct.c_char_p +c_size_t = ct.c_size_t +c_cast = ct.cast + +class af_cfloat_t(ct.Structure): + _fields_ = [("real", ct.c_float), ("imag", ct.c_float)] + +class af_cdouble_t(ct.Structure): + _fields_ = [("real", ct.c_double), ("imag", ct.c_double)] + + +AF_VER_MAJOR = '3' +FORGE_VER_MAJOR = '1' + +# Work around for unexpected architectures +if 'c_dim_t_forced' in globals(): + global c_dim_t_forced + c_dim_t = c_dim_t_forced +else: + # dim_t is long long by default + c_dim_t = c_longlong_t + # Change to int for 32 bit x86 and amr architectures + if (platform.architecture()[0][0:2] == '32' and + (platform.machine()[-2:] == '86' or + platform.machine()[0:3] == 'arm')): + c_dim_t = c_int_t try: from enum import Enum as _Enum def _Enum_Type(v): return v -except: +except ImportError: + class _MetaEnum(type): + def __init__(cls, name, bases, attrs): + for attrname, attrvalue in attrs.iteritems(): + if name != '_Enum' and isinstance(attrvalue, _Enum_Type): + attrvalue.__class__ = cls + attrs[attrname] = attrvalue + class _Enum(object): - pass + __metaclass__ = _MetaEnum class _Enum_Type(object): def __init__(self, v): @@ -31,7 +81,7 @@ class ERR(_Enum): Error values. For internal use only. """ - NONE = _Enum_Type(0) + NONE = _Enum_Type(0) #100-199 Errors in environment NO_MEM = _Enum_Type(101) @@ -45,6 +95,7 @@ class ERR(_Enum): TYPE = _Enum_Type(204) DIFF_TYPE = _Enum_Type(205) BATCH = _Enum_Type(207) + DEVICE = _Enum_Type(208) # 300-399 Errors for missing software features NOT_SUPPORTED = _Enum_Type(301) @@ -54,6 +105,7 @@ class ERR(_Enum): # 400-499 Errors for missing hardware features NO_DBL = _Enum_Type(401) NO_GFX = _Enum_Type(402) + NO_HALF = _Enum_Type(403) # 500-599 Errors specific to the heterogeneous API LOAD_LIB = _Enum_Type(501) @@ -80,6 +132,7 @@ class Dtype(_Enum): u64 = _Enum_Type(9) s16 = _Enum_Type(10) u16 = _Enum_Type(11) + f16 = _Enum_Type(12) class Source(_Enum): """ @@ -92,11 +145,16 @@ class INTERP(_Enum): """ Interpolation method """ - NEAREST = _Enum_Type(0) - LINEAR = _Enum_Type(1) - BILINEAR = _Enum_Type(2) - CUBIC = _Enum_Type(3) - LOWER = _Enum_Type(4) + NEAREST = _Enum_Type(0) + LINEAR = _Enum_Type(1) + BILINEAR = _Enum_Type(2) + CUBIC = _Enum_Type(3) + LOWER = _Enum_Type(4) + LINEAR_COSINE = _Enum_Type(5) + BILINEAR_COSINE = _Enum_Type(6) + BICUBIC = _Enum_Type(7) + CUBIC_SPLINE = _Enum_Type(8) + BICUBIC_SPLINE = _Enum_Type(9) class PAD(_Enum): """ @@ -104,6 +162,8 @@ class PAD(_Enum): """ ZERO = _Enum_Type(0) SYM = _Enum_Type(1) + CLAMP_TO_EDGE = _Enum_Type(2) + PERIODIC = _Enum_Type(3) class CONNECTIVITY(_Enum): """ @@ -127,6 +187,15 @@ class CONV_DOMAIN(_Enum): SPATIAL = _Enum_Type(1) FREQ = _Enum_Type(2) +class CONV_GRADIENT(_Enum): + """ + Convolution gradient type + """ + DEFAULT = _Enum_Type(0) + FILTER = _Enum_Type(1) + DATA = _Enum_Type(2) + BIAS = _Enum_Type(3) + class MATCH(_Enum): """ Match type @@ -315,25 +384,137 @@ class BACKEND(_Enum): CUDA = _Enum_Type(2) OPENCL = _Enum_Type(4) -def _setup(): - import platform - import os +class MARKER(_Enum): + """ + Markers used for different points in graphics plots + """ + NONE = _Enum_Type(0) + POINT = _Enum_Type(1) + CIRCLE = _Enum_Type(2) + SQUARE = _Enum_Type(3) + TRIANGE = _Enum_Type(4) + CROSS = _Enum_Type(5) + PLUS = _Enum_Type(6) + STAR = _Enum_Type(7) + +class MOMENT(_Enum): + """ + Image Moments types + """ + M00 = _Enum_Type(1) + M01 = _Enum_Type(2) + M10 = _Enum_Type(4) + M11 = _Enum_Type(8) + FIRST_ORDER = _Enum_Type(15) + +class BINARYOP(_Enum): + """ + Binary Operators + """ + ADD = _Enum_Type(0) + MUL = _Enum_Type(1) + MIN = _Enum_Type(2) + MAX = _Enum_Type(3) + +class RANDOM_ENGINE(_Enum): + """ + Random engine types + """ + PHILOX_4X32_10 = _Enum_Type(100) + THREEFRY_2X32_16 = _Enum_Type(200) + MERSENNE_GP11213 = _Enum_Type(300) + PHILOX = PHILOX_4X32_10 + THREEFRY = THREEFRY_2X32_16 + DEFAULT = PHILOX + +class STORAGE(_Enum): + """ + Matrix Storage types + """ + DENSE = _Enum_Type(0) + CSR = _Enum_Type(1) + CSC = _Enum_Type(2) + COO = _Enum_Type(3) + +class CANNY_THRESHOLD(_Enum): + """ + Canny Edge Threshold types + """ + MANUAL = _Enum_Type(0) + AUTO_OTSU = _Enum_Type(1) + +class FLUX(_Enum): + """ + Flux functions + """ + DEFAULT = _Enum_Type(0) + QUADRATIC = _Enum_Type(1) + EXPONENTIAL = _Enum_Type(2) + +class DIFFUSION(_Enum): + """ + Diffusion equations + """ + DEFAULT = _Enum_Type(0) + GRAD = _Enum_Type(1) + MCDE = _Enum_Type(2) + +class TOPK(_Enum): + """ + Top-K ordering + """ + DEFAULT = _Enum_Type(0) + MIN = _Enum_Type(1) + MAX = _Enum_Type(2) + +class ITERATIVE_DECONV(_Enum): + """ + Iterative deconvolution algorithm + """ + DEFAULT = _Enum_Type(0) + LANDWEBER = _Enum_Type(1) + RICHARDSONLUCY = _Enum_Type(2) + +class INVERSE_DECONV(_Enum): + """ + Inverse deconvolution algorithm + """ + DEFAULT = _Enum_Type(0) + TIKHONOV = _Enum_Type(1) + +class VARIANCE(_Enum): + """ + Variance bias type + """ + DEFAULT = _Enum_Type(0) + SAMPLE = _Enum_Type(1) + POPULATION = _Enum_Type(2) +class CUBLAS_MATH_MODE(_Enum): + """ + Enable Tensor Core usage if available on CUDA backend GPUs + """ + DEFAULT = _Enum_Type(0) + TENSOR_OP = _Enum_Type(1) + +_VER_MAJOR_PLACEHOLDER = "__VER_MAJOR__" + +def _setup(): platform_name = platform.system() try: - AF_SEARCH_PATH = os.environ['AF_PATH'] - except: - AF_SEARCH_PATH = None - pass + AF_PATH = os.environ['AF_PATH'] + except KeyError: + AF_PATH = None + + AF_SEARCH_PATH = AF_PATH try: CUDA_PATH = os.environ['CUDA_PATH'] - except: + except KeyError: CUDA_PATH= None - pass - CUDA_EXISTS = False + CUDA_FOUND = False assert(len(platform_name) >= 3) if platform_name == 'Windows' or platform_name[:3] == 'CYG': @@ -350,50 +531,90 @@ def _setup(): ct.windll.kernel32.SetErrorMode(0x0001 | 0x0002) if AF_SEARCH_PATH is None: - AF_SEARCH_PATH="C:/Program Files/ArrayFire/v3/" + AF_SEARCH_PATH = "C:/Program Files/ArrayFire/v" + AF_VER_MAJOR +"/" if CUDA_PATH is not None: - CUDA_EXISTS = os.path.isdir(CUDA_PATH + '/bin') and os.path.isdir(CUDA_PATH + '/nvvm/bin/') + CUDA_FOUND = os.path.isdir(CUDA_PATH + '/bin') and os.path.isdir(CUDA_PATH + '/nvvm/bin/') elif platform_name == 'Darwin': ## OSX specific setup pre = 'lib' - post = '.dylib' + post = '.' + _VER_MAJOR_PLACEHOLDER + '.dylib' if AF_SEARCH_PATH is None: - AF_SEARCH_PATH='/usr/local/' + if os.path.exists('/opt/arrayfire'): + AF_SEARCH_PATH = '/opt/arrayfire/' + else: + AF_SEARCH_PATH = '/usr/local/' if CUDA_PATH is None: CUDA_PATH='/usr/local/cuda/' - CUDA_EXISTS = os.path.isdir(CUDA_PATH + '/lib') and os.path.isdir(CUDA_PATH + '/nvvm/lib') + CUDA_FOUND = os.path.isdir(CUDA_PATH + '/lib') and os.path.isdir(CUDA_PATH + '/nvvm/lib') elif platform_name == 'Linux': pre = 'lib' - post = '.so' + post = '.so.' + _VER_MAJOR_PLACEHOLDER if AF_SEARCH_PATH is None: - AF_SEARCH_PATH='/opt/arrayfire-3/' + if os.path.exists('/opt/arrayfire-' + AF_VER_MAJOR + '/'): + AF_SEARCH_PATH = '/opt/arrayfire-' + AF_VER_MAJOR + '/' + elif os.path.exists('/opt/arrayfire/'): + AF_SEARCH_PATH = '/opt/arrayfire/' + else: + AF_SEARCH_PATH = '/usr/local/' if CUDA_PATH is None: CUDA_PATH='/usr/local/cuda/' - if platform.architecture()[0][:2] == 64: - CUDA_EXISTS = os.path.isdir(CUDA_PATH + '/lib64') and os.path.isdir(CUDA_PATH + '/nvvm/lib64') + if platform.architecture()[0][:2] == '64': + CUDA_FOUND = os.path.isdir(CUDA_PATH + '/lib64') and os.path.isdir(CUDA_PATH + '/nvvm/lib64') else: - CUDA_EXISTS = os.path.isdir(CUDA_PATH + '/lib') and os.path.isdir(CUDA_PATH + '/nvvm/lib') + CUDA_FOUND = os.path.isdir(CUDA_PATH + '/lib') and os.path.isdir(CUDA_PATH + '/nvvm/lib') else: raise OSError(platform_name + ' not supported') - return pre, post, AF_SEARCH_PATH, CUDA_EXISTS + return pre, post, AF_PATH, AF_SEARCH_PATH, CUDA_FOUND class _clibrary(object): - def __libname(self, name, head='af'): - libname = self.__pre + head + name + self.__post - libname_full = self.AF_SEARCH_PATH + '/lib/' + libname - return (libname, libname_full) + def __find_nvrtc_builtins_libname(self, search_path): + filelist = os.listdir(search_path) + for f in filelist: + if 'nvrtc-builtins' in f: + return f + return None + + def __libname(self, name, head='af', ver_major=AF_VER_MAJOR): + post = self.__post.replace(_VER_MAJOR_PLACEHOLDER, ver_major) + libname = self.__pre + head + name + post + + if self.AF_PATH: + if os.path.isdir(self.AF_PATH + '/lib64'): + path_search = self.AF_PATH + '/lib64/' + else: + path_search = self.AF_PATH + '/lib/' + else: + if os.path.isdir(self.AF_SEARCH_PATH + '/lib64'): + path_search = self.AF_SEARCH_PATH + '/lib64/' + else: + path_search = self.AF_SEARCH_PATH + '/lib/' + + if platform.architecture()[0][:2] == '64': + path_site = sys.prefix + '/lib64/' + else: + path_site = sys.prefix + '/lib/' + + path_local = self.AF_PYMODULE_PATH + libpaths = [('', libname), + (path_site, libname), + (path_local,libname)] + if self.AF_PATH: #prefer specified AF_PATH if exists + libpaths.append((path_search, libname)) + else: + libpaths.insert(2, (path_search, libname)) + return libpaths def set_unsafe(self, name): lib = self.__clibs[name] @@ -405,52 +626,99 @@ def __init__(self): more_info_str = "Please look at https://github.com/arrayfire/arrayfire-python/wiki for more information." - pre, post, AF_SEARCH_PATH, CUDA_EXISTS = _setup() + pre, post, AF_PATH, AF_SEARCH_PATH, CUDA_FOUND = _setup() self.__pre = pre self.__post = post + self.AF_PATH = AF_PATH self.AF_SEARCH_PATH = AF_SEARCH_PATH + self.CUDA_FOUND = CUDA_FOUND + + # prefer locally packaged arrayfire libraries if they exist + af_module = __import__(__name__) + self.AF_PYMODULE_PATH = af_module.__path__[0] + '/' if af_module.__path__ else None + self.__name = None - self.__clibs = {'cuda' : None, - 'opencl' : None, - 'cpu' : None, - '' : None} + self.__clibs = {'cuda' : None, + 'opencl' : None, + 'cpu' : None, + 'unified' : None} - self.__backend_map = {0 : 'default', + self.__backend_map = {0 : 'unified', 1 : 'cpu' , 2 : 'cuda' , 4 : 'opencl' } self.__backend_name_map = {'default' : 0, + 'unified' : 0, 'cpu' : 1, 'cuda' : 2, 'opencl' : 4} # Try to pre-load forge library if it exists - libnames = self.__libname('forge', '') + libnames = reversed(self.__libname('forge', head='', ver_major=FORGE_VER_MAJOR)) + + try: + VERBOSE_LOADS = os.environ['AF_VERBOSE_LOADS'] == '1' + except KeyError: + VERBOSE_LOADS = False + for libname in libnames: try: - ct.cdll.LoadLibrary(libname) - except: - pass + full_libname = libname[0] + libname[1] + ct.cdll.LoadLibrary(full_libname) + if VERBOSE_LOADS: + print('Loaded ' + full_libname) + break + except OSError: + if VERBOSE_LOADS: + traceback.print_exc() + print('Unable to load ' + full_libname) + + c_dim4 = c_dim_t*4 + out = c_void_ptr_t(0) + dims = c_dim4(10, 10, 1, 1) # Iterate in reverse order of preference for name in ('cpu', 'opencl', 'cuda', ''): - libnames = self.__libname(name) + libnames = reversed(self.__libname(name)) for libname in libnames: try: - ct.cdll.LoadLibrary(libname) - self.__clibs[name] = ct.CDLL(libname) - self.__name = name - break; - except: - pass + full_libname = libname[0] + libname[1] + + ct.cdll.LoadLibrary(full_libname) + __name = 'unified' if name == '' else name + clib = ct.CDLL(full_libname) + self.__clibs[__name] = clib + err = clib.af_randu(c_pointer(out), 4, c_pointer(dims), Dtype.f32.value) + if (err == ERR.NONE.value): + self.__name = __name + clib.af_release_array(out) + if VERBOSE_LOADS: + print('Loaded ' + full_libname) + + # load nvrtc-builtins library if using cuda + if name == 'cuda': + nvrtc_name = self.__find_nvrtc_builtins_libname(libname[0]) + if nvrtc_name: + ct.cdll.LoadLibrary(libname[0] + nvrtc_name) + + if VERBOSE_LOADS: + print('Loaded ' + libname[0] + nvrtc_name) + else: + if VERBOSE_LOADS: + print('Could not find local nvrtc-builtins libarary') + + break; + except OSError: + if VERBOSE_LOADS: + traceback.print_exc() + print('Unable to load ' + full_libname) if (self.__name is None): - raise RuntimeError("Could not load any ArrayFire libraries.\n" + - more_info_str) + raise RuntimeError("Could not load any ArrayFire libraries.\n" + more_info_str) def get_id(self, name): return self.__backend_name_map[name] @@ -465,7 +733,7 @@ def name(self): return self.__name def is_unified(self): - return self.__name == '' + return self.__name == 'unified' def parse(self, res): lst = [] @@ -474,6 +742,7 @@ def parse(self, res): lst.append(key) return tuple(lst) + backend = _clibrary() def set_backend(name, unsafe=False): @@ -488,8 +757,8 @@ def set_backend(name, unsafe=False): unsafe : optional: bool. Default: False. If False, does not switch backend if current backend is not unified backend. """ - if (backend.is_unified() == False and unsanfe == False): - raise RuntimeError("Can not change backend after loading %s" % name) + if (backend.is_unified() == False and unsafe == False): + raise RuntimeError("Can not change backend to %s after loading %s" % (name, backend.name())) if (backend.is_unified()): safe_call(backend.get().af_set_backend(backend.get_id(name))) @@ -497,6 +766,12 @@ def set_backend(name, unsafe=False): backend.set_unsafe(name) return +def get_backend(): + """ + Return the name of the backend + """ + return backend.name() + def get_backend_id(A): """ Get backend name of an array @@ -511,12 +786,9 @@ def get_backend_id(A): name : str. Backend name """ - if (backend.is_unified()): - backend_id = ct.c_int(BACKEND.DEFAULT.value) - safe_call(backend.get().af_get_backend_id(ct.pointer(backend_id), A.arr)) - return backend.get_name(backend_id.value) - else: - return backend.name() + backend_id = c_int_t(BACKEND.CPU.value) + safe_call(backend.get().af_get_backend_id(c_pointer(backend_id), A.arr)) + return backend.get_name(backend_id.value) def get_backend_count(): """ @@ -528,12 +800,9 @@ def get_backend_count(): count : int Number of available backends """ - if (backend.is_unified()): - count = ct.c_int(0) - safe_call(backend.get().af_get_backend_count(ct.pointer(count))) - return count.value - else: - return 1 + count = c_int_t(0) + safe_call(backend.get().af_get_backend_count(c_pointer(count))) + return count.value def get_available_backends(): """ @@ -545,11 +814,45 @@ def get_available_backends(): names : tuple of strings Names of available backends """ - if (backend.is_unified()): - available = ct.c_int(0) - safe_call(backend.get().af_get_available_backends(ct.pointer(available))) - return backend.parse(int(available.value)) - else: - return (backend.name(),) + available = c_int_t(0) + safe_call(backend.get().af_get_available_backends(c_pointer(available))) + return backend.parse(int(available.value)) + +def get_active_backend(): + """ + Get the current active backend + + name : str. + Backend name + """ + backend_id = c_int_t(BACKEND.CPU.value) + safe_call(backend.get().af_get_active_backend(c_pointer(backend_id))) + return backend.get_name(backend_id.value) + +def get_device_id(A): + """ + Get the device id of the array + + Parameters + ---------- + A : af.Array + + Returns + ---------- + + dev : Integer + id of the device array was created on + """ + device_id = c_int_t(0) + safe_call(backend.get().af_get_device_id(c_pointer(device_id), A.arr)) + return device_id + +def get_size_of(dtype): + """ + Get the size of the type represented by arrayfire.Dtype + """ + size = c_size_t(0) + safe_call(backend.get().af_get_size_of(c_pointer(size), dtype.value)) + return size.value from .util import safe_call diff --git a/arrayfire/ml.py b/arrayfire/ml.py new file mode 100644 index 000000000..7e0fc53de --- /dev/null +++ b/arrayfire/ml.py @@ -0,0 +1,83 @@ +####################################################### +# Copyright (c) 2020, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +""" +Machine learning functions + - Pool 2D, ND, maxpooling, minpooling, meanpooling + - Forward and backward convolution passes +""" + +from .library import * +from .array import * + +def convolve2GradientNN(incoming_gradient, original_signal, original_kernel, convolved_output, stride = (1, 1), padding = (0, 0), dilation = (1, 1), gradType = CONV_GRADIENT.DEFAULT): + """ + Function for calculating backward pass gradient of 2D convolution. + + This function calculates the gradient with respect to the output of the + \ref convolve2NN() function that uses the machine learning formulation + for the dimensions of the signals and filters + + Multiple signals and filters can be batched against each other, however + their dimensions must match. + + Example: + Signals with dimensions: d0 x d1 x d2 x Ns + Filters with dimensions: d0 x d1 x d2 x Nf + + Resulting Convolution: d0 x d1 x Nf x Ns + + Parameters + ----------- + + incoming_gradient: af.Array + - Gradients to be distributed in backwards pass + + original_signal: af.Array + - A 2 dimensional signal or batch of 2 dimensional signals. + + original_kernel: af.Array + - A 2 dimensional kernel or batch of 2 dimensional kernels. + + convolved_output: af.Array + - output of forward pass of convolution + + stride: tuple of ints. default: (1, 1). + - Specifies how much to stride along each dimension + + padding: tuple of ints. default: (0, 0). + - Specifies signal padding along each dimension + + dilation: tuple of ints. default: (1, 1). + - Specifies how much to dilate kernel along each dimension before convolution + + Returns + -------- + + output: af.Array + - Gradient wrt/requested gradient type + + """ + output = Array() + stride_dim = dim4(stride[0], stride[1]) + padding_dim = dim4(padding[0], padding[1]) + dilation_dim = dim4(dilation[0], dilation[1]) + + safe_call(backend.get().af_convolve2_gradient_nn( + c_pointer(output.arr), + incoming_gradient.arr, + original_signal.arr, + original_kernel.arr, + convolved_output.arr, + 2, c_pointer(stride_dim), + 2, c_pointer(padding_dim), + 2, c_pointer(dilation_dim), + gradType.value)) + return output + diff --git a/arrayfire/opencl.py b/arrayfire/opencl.py index 5c075df3b..266e1095a 100644 --- a/arrayfire/opencl.py +++ b/arrayfire/opencl.py @@ -13,6 +13,30 @@ This module provides interoperability with other OpenCL libraries. """ +from .util import * +from .library import (_Enum, _Enum_Type) + +class DEVICE_TYPE(_Enum): + """ + ArrayFire wrapper for CL_DEVICE_TYPE + """ + CPU = _Enum_Type(1<<1) + GPU = _Enum_Type(1<<2) + ACC = _Enum_Type(1<<3) + UNKNOWN = _Enum_Type(-1) + +class PLATFORM(_Enum): + """ + ArrayFire enum for common platforms + """ + AMD = _Enum_Type(0) + APPLE = _Enum_Type(1) + INTEL = _Enum_Type(2) + NVIDIA = _Enum_Type(3) + BEIGNET = _Enum_Type(4) + POCL = _Enum_Type(5) + UNKNOWN = _Enum_Type(-1) + def get_context(retain=False): """ Get the current OpenCL context being used by ArrayFire. @@ -30,13 +54,13 @@ def get_context(retain=False): import ctypes as ct from .util import safe_call as safe_call - from .library import backend as backend + from .library import backend if (backend.name() != "opencl"): raise RuntimeError("Invalid backend loaded") - context = ct.c_void_p(0) - safe_call(backend.get().afcl_get_context(ct.pointer(context), retain)) + context = c_void_ptr_t(0) + safe_call(backend.get().afcl_get_context(c_pointer(context), retain)) return context.value def get_queue(retain): @@ -56,13 +80,13 @@ def get_queue(retain): import ctypes as ct from .util import safe_call as safe_call - from .library import backend as backend + from .library import backend if (backend.name() != "opencl"): raise RuntimeError("Invalid backend loaded") - queue = ct.c_int(0) - safe_call(backend.get().afcl_get_queue(ct.pointer(queue), retain)) + queue = c_int_t(0) + safe_call(backend.get().afcl_get_queue(c_pointer(queue), retain)) return queue.value def get_device_id(): @@ -78,13 +102,13 @@ def get_device_id(): import ctypes as ct from .util import safe_call as safe_call - from .library import backend as backend + from .library import backend if (backend.name() != "opencl"): raise RuntimeError("Invalid backend loaded") - idx = ct.c_int(0) - safe_call(backend.get().afcl_get_device_id(ct.pointer(idx))) + idx = c_int_t(0) + safe_call(backend.get().afcl_get_device_id(c_pointer(idx))) return idx.value def set_device_id(idx): @@ -100,10 +124,120 @@ def set_device_id(idx): import ctypes as ct from .util import safe_call as safe_call - from .library import backend as backend + from .library import backend if (backend.name() != "opencl"): raise RuntimeError("Invalid backend loaded") safe_call(backend.get().afcl_set_device_id(idx)) return + +def add_device_context(dev, ctx, que): + """ + Add a new device to arrayfire opencl device manager + + Parameters + ---------- + + dev : cl_device_id + + ctx : cl_context + + que : cl_command_queue + + """ + import ctypes as ct + from .util import safe_call as safe_call + from .library import backend + + if (backend.name() != "opencl"): + raise RuntimeError("Invalid backend loaded") + + safe_call(backend.get().afcl_add_device_context(dev, ctx, que)) + +def set_device_context(dev, ctx): + """ + Set a device as current active device + + Parameters + ---------- + + dev : cl_device_id + + ctx : cl_context + + """ + import ctypes as ct + from .util import safe_call as safe_call + from .library import backend + + if (backend.name() != "opencl"): + raise RuntimeError("Invalid backend loaded") + + safe_call(backend.get().afcl_set_device_context(dev, ctx)) + +def delete_device_context(dev, ctx): + """ + Delete a device + + Parameters + ---------- + + dev : cl_device_id + + ctx : cl_context + + """ + import ctypes as ct + from .util import safe_call as safe_call + from .library import backend + + if (backend.name() != "opencl"): + raise RuntimeError("Invalid backend loaded") + + safe_call(backend.get().afcl_delete_device_context(dev, ctx)) + + +_to_device_type = {DEVICE_TYPE.CPU.value : DEVICE_TYPE.CPU, + DEVICE_TYPE.GPU.value : DEVICE_TYPE.GPU, + DEVICE_TYPE.ACC.value : DEVICE_TYPE.ACC, + DEVICE_TYPE.UNKNOWN.value : DEVICE_TYPE.UNKNOWN} + +_to_platform = {PLATFORM.AMD.value : PLATFORM.AMD, + PLATFORM.APPLE.value : PLATFORM.APPLE, + PLATFORM.INTEL.value : PLATFORM.INTEL, + PLATFORM.NVIDIA.value : PLATFORM.NVIDIA, + PLATFORM.BEIGNET.value : PLATFORM.BEIGNET, + PLATFORM.POCL.value : PLATFORM.POCL, + PLATFORM.UNKNOWN.value : PLATFORM.UNKNOWN} + + +def get_device_type(): + """ + Get opencl device type + """ + import ctypes as ct + from .util import safe_call as safe_call + from .library import backend + + if (backend.name() != "opencl"): + raise RuntimeError("Invalid backend loaded") + + res = c_int_t(DEVICE_TYPE.UNKNOWN.value) + safe_call(backend.get().afcl_get_device_type(c_pointer(res))) + return _to_device_type[res.value] + +def get_platform(): + """ + Get opencl platform + """ + import ctypes as ct + from .util import safe_call as safe_call + from .library import backend + + if (backend.name() != "opencl"): + raise RuntimeError("Invalid backend loaded") + + res = c_int_t(PLATFORM.UNKNOWN.value) + safe_call(backend.get().afcl_get_platform(c_pointer(res))) + return _to_platform[res.value] diff --git a/arrayfire/random.py b/arrayfire/random.py new file mode 100644 index 000000000..179833db8 --- /dev/null +++ b/arrayfire/random.py @@ -0,0 +1,232 @@ +####################################################### +# Copyright (c) 2015, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +""" +Random engine class and functions to generate random numbers. +""" + +from .library import * +from .array import * +import numbers + +class Random_Engine(object): + """ + Class to handle random number generator engines. + + Parameters + ---------- + + engine_type : optional: RANDOME_ENGINE. default: RANDOM_ENGINE.PHILOX + - Specifies the type of random engine to be created. Can be one of: + - RANDOM_ENGINE.PHILOX_4X32_10 + - RANDOM_ENGINE.THREEFRY_2X32_16 + - RANDOM_ENGINE.MERSENNE_GP11213 + - RANDOM_ENGINE.PHILOX (same as RANDOM_ENGINE.PHILOX_4X32_10) + - RANDOM_ENGINE.THREEFRY (same as RANDOM_ENGINE.THREEFRY_2X32_16) + - RANDOM_ENGINE.DEFAULT + - Not used if engine is not None + + seed : optional int. default: 0 + - Specifies the seed for the random engine + - Not used if engine is not None + + engine : optional ctypes.c_void_p. default: None. + - Used a handle created by the C api to create the Random_Engine. + """ + + def __init__(self, engine_type = RANDOM_ENGINE.PHILOX, seed = 0, engine = None): + if (engine is None): + self.engine = c_void_ptr_t(0) + safe_call(backend.get().af_create_random_engine(c_pointer(self.engine), engine_type.value, c_longlong_t(seed))) + else: + self.engine = engine + + def __del__(self): + safe_call(backend.get().af_release_random_engine(self.engine)) + + def set_type(self, engine_type): + """ + Set the type of the random engine. + """ + safe_call(backend.get().af_random_engine_set_type(c_pointer(self.engine), engine_type.value)) + + def get_type(self): + """ + Get the type of the random engine. + """ + __to_random_engine_type = [RANDOM_ENGINE.PHILOX_4X32_10, + RANDOM_ENGINE.THREEFRY_2X32_16, + RANDOM_ENGINE.MERSENNE_GP11213] + rty = c_int_t(RANDOM_ENGINE.PHILOX.value) + safe_call(backend.get().af_random_engine_get_type(c_pointer(rty), self.engine)) + return __to_random_engine_type[rty] + + def set_seed(self, seed): + """ + Set the seed for the random engine. + """ + safe_call(backend.get().af_random_engine_set_seed(c_pointer(self.engine), c_longlong_t(seed))) + + def get_seed(self): + """ + Get the seed for the random engine. + """ + seed = c_longlong_t(0) + safe_call(backend.get().af_random_engine_get_seed(c_pointer(seed), self.engine)) + return seed.value + +def randu(d0, d1=None, d2=None, d3=None, dtype=Dtype.f32, engine=None): + """ + Create a multi dimensional array containing values from a uniform distribution. + + Parameters + ---------- + d0 : int. + Length of first dimension. + + d1 : optional: int. default: None. + Length of second dimension. + + d2 : optional: int. default: None. + Length of third dimension. + + d3 : optional: int. default: None. + Length of fourth dimension. + + dtype : optional: af.Dtype. default: af.Dtype.f32. + Data type of the array. + + engine : optional: Random_Engine. default: None. + If engine is None, uses a default engine created by arrayfire. + + Returns + ------- + + out : af.Array + Multi dimensional array whose elements are sampled uniformly between [0, 1]. + - If d1 is None, `out` is 1D of size (d0,). + - If d1 is not None and d2 is None, `out` is 2D of size (d0, d1). + - If d1 and d2 are not None and d3 is None, `out` is 3D of size (d0, d1, d2). + - If d1, d2, d3 are all not None, `out` is 4D of size (d0, d1, d2, d3). + """ + out = Array() + dims = dim4(d0, d1, d2, d3) + + if engine is None: + safe_call(backend.get().af_randu(c_pointer(out.arr), 4, c_pointer(dims), dtype.value)) + else: + safe_call(backend.get().af_random_uniform(c_pointer(out.arr), 4, c_pointer(dims), dtype.value, engine.engine)) + + return out + +def randn(d0, d1=None, d2=None, d3=None, dtype=Dtype.f32, engine=None): + """ + Create a multi dimensional array containing values from a normal distribution. + + Parameters + ---------- + d0 : int. + Length of first dimension. + + d1 : optional: int. default: None. + Length of second dimension. + + d2 : optional: int. default: None. + Length of third dimension. + + d3 : optional: int. default: None. + Length of fourth dimension. + + dtype : optional: af.Dtype. default: af.Dtype.f32. + Data type of the array. + + engine : optional: Random_Engine. default: None. + If engine is None, uses a default engine created by arrayfire. + + Returns + ------- + + out : af.Array + Multi dimensional array whose elements are sampled from a normal distribution with mean 0 and sigma of 1. + - If d1 is None, `out` is 1D of size (d0,). + - If d1 is not None and d2 is None, `out` is 2D of size (d0, d1). + - If d1 and d2 are not None and d3 is None, `out` is 3D of size (d0, d1, d2). + - If d1, d2, d3 are all not None, `out` is 4D of size (d0, d1, d2, d3). + """ + + out = Array() + dims = dim4(d0, d1, d2, d3) + + if engine is None: + safe_call(backend.get().af_randn(c_pointer(out.arr), 4, c_pointer(dims), dtype.value)) + else: + safe_call(backend.get().af_random_normal(c_pointer(out.arr), 4, c_pointer(dims), dtype.value, engine.engine)) + + return out + +def set_seed(seed=0): + """ + Set the seed for the random number generator. + + Parameters + ---------- + seed: int. + Seed for the random number generator + """ + safe_call(backend.get().af_set_seed(c_ulonglong_t(seed))) + +def get_seed(): + """ + Get the seed for the random number generator. + + Returns + ------- + seed: int. + Seed for the random number generator + """ + seed = c_ulonglong_t(0) + safe_call(backend.get().af_get_seed(c_pointer(seed))) + return seed.value + +def set_default_random_engine_type(engine_type): + """ + Set random engine type for default random engine. + + Parameters + ---------- + engine_type : RANDOME_ENGINE. + - Specifies the type of random engine to be created. Can be one of: + - RANDOM_ENGINE.PHILOX_4X32_10 + - RANDOM_ENGINE.THREEFRY_2X32_16 + - RANDOM_ENGINE.MERSENNE_GP11213 + - RANDOM_ENGINE.PHILOX (same as RANDOM_ENGINE.PHILOX_4X32_10) + - RANDOM_ENGINE.THREEFRY (same as RANDOM_ENGINE.THREEFRY_2X32_16) + - RANDOM_ENGINE.DEFAULT + + Note + ---- + + This only affects randu and randn when a random engine is not specified. + """ + safe_call(backend.get().af_set_default_random_engine_type(c_pointer(self.engine), engine_type.value)) + +def get_default_random_engine(): + """ + Get the default random engine + + Returns + ------- + + The default random engine used by randu and randn + """ + engine = c_void_ptr_t(0) + default_engine = c_void_ptr_t(0) + safe_call(backend.get().af_get_default_random_engine(c_pointer(default_engine))) + safe_call(backend.get().af_retain_random_engine(c_pointer(engine), default_engine)) + return Random_Engine(engine=engine) diff --git a/arrayfire/signal.py b/arrayfire/signal.py index b66386954..35e0fba87 100644 --- a/arrayfire/signal.py +++ b/arrayfire/signal.py @@ -8,24 +8,39 @@ ######################################################## """ -signal processing functions for arrayfire. +Signal processing functions (fft, convolve, etc). """ from .library import * from .array import * +from .bcast import broadcast -def approx1(signal, pos0, method=INTERP.LINEAR, off_grid=0.0): +@broadcast +def _scale_pos_axis0(x_curr, x_orig): + x0 = x_orig[0, 0, 0, 0] + dx = x_orig[1, 0, 0, 0] - x0 + return((x_curr - x0) / dx) + +@broadcast +def _scale_pos_axis1(y_curr, y_orig): + y0 = y_orig[0, 0, 0, 0] + dy = y_orig[0, 1, 0, 0] - y0 + return((y_curr - y0) / dy) + +def approx1(signal, x, method=INTERP.LINEAR, off_grid=0.0, xp = None, output = None): """ - Interpolate along a single dimension. + Interpolate along a single dimension.Interpolation is performed along axis 0 + of the input array. Parameters ---------- signal: af.Array - A 1 dimensional signal or batch of 1 dimensional signals. + Input signal array (signal = f(x)) - pos0 : af.Array - Locations of the interpolation points. + x: af.Array + The x-coordinates of the interpolation points. The interpolation + function is queried at these set of points. method: optional: af.INTERP. default: af.INTERP.LINEAR. Interpolation method. @@ -33,40 +48,125 @@ def approx1(signal, pos0, method=INTERP.LINEAR, off_grid=0.0): off_grid: optional: scalar. default: 0.0. The value used for positions outside the range. + xp : af.Array + The x-coordinates of the input data points + + output: None or af.Array + Optional preallocated output array. If it is a sub-array of an existing af_array, + only the corresponding portion of the af_array will be overwritten + Returns ------- output: af.Array Values calculated at interpolation points. + Note ----- - + This holds applicable when x_input isn't provided: The initial measurements are assumed to have taken place at equal steps between [0, N - 1], where N is the length of the first dimension of `signal`. + """ + + if output is None: + output = Array() + + if(xp is not None): + pos0 = _scale_pos_axis0(x, xp) + else: + pos0 = x + + safe_call(backend.get().af_approx1(c_pointer(output.arr), signal.arr, pos0.arr, + method.value, c_float_t(off_grid))) + + else: + if(xp is not None): + pos0 = _scale_pos_axis0(x, xp) + else: + pos0 = x + safe_call(backend.get().af_approx1_v2(c_pointer(output.arr), signal.arr, pos0.arr, + method.value, c_float_t(off_grid))) + return output +def approx1_uniform(signal, x, interp_dim, idx_start, idx_step, method=INTERP.LINEAR, off_grid=0.0, output = None): """ - output = Array() - safe_call(backend.get().af_approx1(ct.pointer(output.arr), signal.arr, pos0.arr, - method.value, ct.c_double(off_grid))) + Interpolation on one dimensional signals along specified dimension. + + af_approx1_uniform() accepts the dimension to perform the interpolation along the input. + It also accepts start and step values which define the uniform range of corresponding indices. + + Parameters + ---------- + + signal: af.Array + Input signal array (signal = f(x)) + + x: af.Array + The x-coordinates of the interpolation points. The interpolation + function is queried at these set of points. + + interp_dim: scalar + is the dimension to perform interpolation across. + + idx_start: scalar + is the first index value along interp_dim. + + idx_step: scalar + is the uniform spacing value between subsequent indices along interp_dim. + + method: optional: af.INTERP. default: af.INTERP.LINEAR. + Interpolation method. + + off_grid: optional: scalar. default: 0.0. + The value used for positions outside the range. + + output: None or af.Array + Optional preallocated output array. If it is a sub-array of an existing af_array, + only the corresponding portion of the af_array will be overwritten + + Returns + ------- + + output: af.Array + Values calculated at interpolation points. + + """ + + if output is None: + output = Array() + + safe_call(backend.get().af_approx1_uniform(c_pointer(output.arr), signal.arr, x.arr, + c_dim_t(interp_dim), c_double_t(idx_start), c_double_t(idx_step), + method.value, c_float_t(off_grid))) + else: + safe_call(backend.get().af_approx1_uniform_v2(c_pointer(output.arr), signal.arr, x.arr, + c_dim_t(interp_dim), c_double_t(idx_start), c_double_t(idx_step), + method.value, c_float_t(off_grid))) return output -def approx2(signal, pos0, pos1, method=INTERP.LINEAR, off_grid=0.0): + +def approx2(signal, x, y, + method=INTERP.LINEAR, off_grid=0.0, xp = None, yp = None, output = None): """ - Interpolate along a two dimension. + Interpolate along a two dimension.Interpolation is performed along axes 0 and 1 + of the input array. Parameters ---------- signal: af.Array - A 2 dimensional signal or batch of 2 dimensional signals. + Input signal array (signal = f(x, y)) - pos0 : af.Array - Locations of the interpolation points along the first dimension. + x : af.Array + The x-coordinates of the interpolation points. The interpolation + function is queried at these set of points. - pos1 : af.Array - Locations of the interpolation points along the second dimension. + + y : af.Array + The y-coordinates of the interpolation points. The interpolation + function is queried at these set of points. method: optional: af.INTERP. default: af.INTERP.LINEAR. Interpolation method. @@ -74,6 +174,18 @@ def approx2(signal, pos0, pos1, method=INTERP.LINEAR, off_grid=0.0): off_grid: optional: scalar. default: 0.0. The value used for positions outside the range. + xp : af.Array + The x-coordinates of the input data points. The convention followed is that + the x-coordinates vary along axis 0 + + yp : af.Array + The y-coordinates of the input data points. The convention followed is that + the y-coordinates vary along axis 1 + + output: None or af.Array + Optional preallocated output array. If it is a sub-array of an existing af_array, + only the corresponding portion of the af_array will be overwritten + Returns ------- @@ -82,18 +194,121 @@ def approx2(signal, pos0, pos1, method=INTERP.LINEAR, off_grid=0.0): Note ----- + This holds applicable when x_input/y_input isn't provided: The initial measurements are assumed to have taken place at equal steps between [(0,0) - [M - 1, N - 1]] where M is the length of the first dimension of `signal`, and N is the length of the second dimension of `signal`. + """ + + if output is None: + output = Array() + + if(xp is not None): + pos0 = _scale_pos_axis0(x, xp) + else: + pos0 = x + + if(yp is not None): + pos1 = _scale_pos_axis1(y, yp) + else: + pos1 = y + + safe_call(backend.get().af_approx2(c_pointer(output.arr), signal.arr, + pos0.arr, pos1.arr, method.value, c_float_t(off_grid))) + else: + if(xp is not None): + pos0 = _scale_pos_axis0(x, xp) + else: + pos0 = x + + if(yp is not None): + pos1 = _scale_pos_axis1(y, yp) + else: + pos1 = y + safe_call(backend.get().af_approx2_v2(c_pointer(output.arr), signal.arr, + pos0.arr, pos1.arr, method.value, c_float_t(off_grid))) + return output + +def approx2_uniform(signal, pos0, interp_dim0, idx_start0, idx_step0, pos1, interp_dim1, idx_start1, idx_step1, + method=INTERP.LINEAR, off_grid=0.0, output = None): """ - output = Array() - safe_call(backend.get().af_approx2(ct.pointer(output.arr), signal.arr, - pos0.arr, pos1.arr, method.value, ct.c_double(off_grid))) + Interpolate along two uniformly spaced dimensions of the input array. + af_approx2_uniform() accepts two dimensions to perform the interpolation along the input. + It also accepts start and step values which define the uniform range of corresponding indices. + + Parameters + ---------- + + signal: af.Array + Input signal array (signal = f(x, y)) + + pos0 : af.Array + positions of the interpolation points along interp_dim0. + + interp_dim0: scalar + is the first dimension to perform interpolation across. + + idx_start0: scalar + is the first index value along interp_dim0. + + idx_step0: scalar + is the uniform spacing value between subsequent indices along interp_dim0. + + pos1 : af.Array + positions of the interpolation points along interp_dim1. + + interp_dim1: scalar + is the second dimension to perform interpolation across. + + idx_start1: scalar + is the first index value along interp_dim1. + + idx_step1: scalar + is the uniform spacing value between subsequent indices along interp_dim1. + + method: optional: af.INTERP. default: af.INTERP.LINEAR. + Interpolation method. + + off_grid: optional: scalar. default: 0.0. + The value used for positions outside the range. + + output: None or af.Array + Optional preallocated output array. If it is a sub-array of an existing af_array, + only the corresponding portion of the af_array will be overwritten + + Returns + ------- + + output: af.Array + Values calculated at interpolation points. + + Note + ----- + This holds applicable when x_input/y_input isn't provided: + + The initial measurements are assumed to have taken place at equal steps between [(0,0) - [M - 1, N - 1]] + where M is the length of the first dimension of `signal`, + and N is the length of the second dimension of `signal`. + """ + + if output is None: + output = Array() + safe_call(backend.get().af_approx2_uniform(c_pointer(output.arr), signal.arr, + pos0.arr, c_dim_t(interp_dim0), c_double_t(idx_start0), c_double_t(idx_step0), + pos1.arr, c_dim_t(interp_dim1), c_double_t(idx_start1), c_double_t(idx_step1), + method.value, c_float_t(off_grid))) + else: + safe_call(backend.get().af_approx2_uniform_v2(c_pointer(output.arr), signal.arr, + pos0.arr, c_dim_t(interp_dim0), c_double_t(idx_start0), c_double_t(idx_step0), + pos1.arr, c_dim_t(interp_dim1), c_double_t(idx_start1), c_double_t(idx_step1), + method.value, c_float_t(off_grid))) + return output + def fft(signal, dim0 = None , scale = None): """ Fast Fourier Transform: 1D @@ -127,7 +342,7 @@ def fft(signal, dim0 = None , scale = None): scale = 1.0 output = Array() - safe_call(backend.get().af_fft(ct.pointer(output.arr), signal.arr, ct.c_double(scale), ct.c_longlong(dim0))) + safe_call(backend.get().af_fft(c_pointer(output.arr), signal.arr, c_double_t(scale), c_dim_t(dim0))) return output def fft2(signal, dim0 = None, dim1 = None , scale = None): @@ -169,8 +384,8 @@ def fft2(signal, dim0 = None, dim1 = None , scale = None): scale = 1.0 output = Array() - safe_call(backend.get().af_fft2(ct.pointer(output.arr), signal.arr, ct.c_double(scale), - ct.c_longlong(dim0), ct.c_longlong(dim1))) + safe_call(backend.get().af_fft2(c_pointer(output.arr), signal.arr, c_double_t(scale), + c_dim_t(dim0), c_dim_t(dim1))) return output def fft3(signal, dim0 = None, dim1 = None , dim2 = None, scale = None): @@ -219,8 +434,8 @@ def fft3(signal, dim0 = None, dim1 = None , dim2 = None, scale = None): scale = 1.0 output = Array() - safe_call(backend.get().af_fft3(ct.pointer(output.arr), signal.arr, ct.c_double(scale), - ct.c_longlong(dim0), ct.c_longlong(dim1), ct.c_longlong(dim2))) + safe_call(backend.get().af_fft3(c_pointer(output.arr), signal.arr, c_double_t(scale), + c_dim_t(dim0), c_dim_t(dim1), c_dim_t(dim2))) return output def ifft(signal, dim0 = None , scale = None): @@ -261,7 +476,7 @@ def ifft(signal, dim0 = None , scale = None): scale = 1.0/float(dim0) output = Array() - safe_call(backend.get().af_ifft(ct.pointer(output.arr), signal.arr, ct.c_double(scale), ct.c_longlong(dim0))) + safe_call(backend.get().af_ifft(c_pointer(output.arr), signal.arr, c_double_t(scale), c_dim_t(dim0))) return output def ifft2(signal, dim0 = None, dim1 = None , scale = None): @@ -311,8 +526,8 @@ def ifft2(signal, dim0 = None, dim1 = None , scale = None): scale = 1.0/float(dim0 * dim1) output = Array() - safe_call(backend.get().af_ifft2(ct.pointer(output.arr), signal.arr, ct.c_double(scale), - ct.c_longlong(dim0), ct.c_longlong(dim1))) + safe_call(backend.get().af_ifft2(c_pointer(output.arr), signal.arr, c_double_t(scale), + c_dim_t(dim0), c_dim_t(dim1))) return output def ifft3(signal, dim0 = None, dim1 = None , dim2 = None, scale = None): @@ -369,8 +584,8 @@ def ifft3(signal, dim0 = None, dim1 = None , dim2 = None, scale = None): scale = 1.0 / float(dim0 * dim1 * dim2) output = Array() - safe_call(backend.get().af_ifft3(ct.pointer(output.arr), signal.arr, ct.c_double(scale), - ct.c_longlong(dim0), ct.c_longlong(dim1), ct.c_longlong(dim2))) + safe_call(backend.get().af_ifft3(c_pointer(output.arr), signal.arr, c_double_t(scale), + c_dim_t(dim0), c_dim_t(dim1), c_dim_t(dim2))) return output def fft_inplace(signal, scale = None): @@ -392,7 +607,7 @@ def fft_inplace(signal, scale = None): if scale is None: scale = 1.0 - safe_call(backend.get().af_fft_inplace(signal.arr, ct.c_double(scale))) + safe_call(backend.get().af_fft_inplace(signal.arr, c_double_t(scale))) def fft2_inplace(signal, scale = None): """ @@ -413,7 +628,7 @@ def fft2_inplace(signal, scale = None): if scale is None: scale = 1.0 - safe_call(backend.get().af_fft2_inplace(signal.arr, ct.c_double(scale))) + safe_call(backend.get().af_fft2_inplace(signal.arr, c_double_t(scale))) def fft3_inplace(signal, scale = None): """ @@ -434,7 +649,7 @@ def fft3_inplace(signal, scale = None): scale = 1.0 output = Array() - safe_call(backend.get().af_fft3_inplace(signal.arr, ct.c_double(scale))) + safe_call(backend.get().af_fft3_inplace(signal.arr, c_double_t(scale))) def ifft_inplace(signal, scale = None): """ @@ -455,7 +670,7 @@ def ifft_inplace(signal, scale = None): dim0 = signal.dims()[0] scale = 1.0/float(dim0) - safe_call(backend.get().af_ifft_inplace(signal.arr, ct.c_double(scale))) + safe_call(backend.get().af_ifft_inplace(signal.arr, c_double_t(scale))) def ifft2_inplace(signal, scale = None): """ @@ -479,7 +694,7 @@ def ifft2_inplace(signal, scale = None): dim1 = dims[1] scale = 1.0/float(dim0 * dim1) - safe_call(backend.get().af_ifft2_inplace(signal.arr, ct.c_double(scale))) + safe_call(backend.get().af_ifft2_inplace(signal.arr, c_double_t(scale))) def ifft3_inplace(signal, scale = None): """ @@ -504,7 +719,7 @@ def ifft3_inplace(signal, scale = None): dim2 = dims[2] scale = 1.0 / float(dim0 * dim1 * dim2) - safe_call(backend.get().af_ifft3_inplace(signal.arr, ct.c_double(scale))) + safe_call(backend.get().af_ifft3_inplace(signal.arr, c_double_t(scale))) def fft_r2c(signal, dim0 = None , scale = None): """ @@ -539,7 +754,7 @@ def fft_r2c(signal, dim0 = None , scale = None): scale = 1.0 output = Array() - safe_call(backend.get().af_fft_r2c(ct.pointer(output.arr), signal.arr, ct.c_double(scale), ct.c_longlong(dim0))) + safe_call(backend.get().af_fft_r2c(c_pointer(output.arr), signal.arr, c_double_t(scale), c_dim_t(dim0))) return output def fft2_r2c(signal, dim0 = None, dim1 = None , scale = None): @@ -581,8 +796,8 @@ def fft2_r2c(signal, dim0 = None, dim1 = None , scale = None): scale = 1.0 output = Array() - safe_call(backend.get().af_fft2_r2c(ct.pointer(output.arr), signal.arr, ct.c_double(scale), - ct.c_longlong(dim0), ct.c_longlong(dim1))) + safe_call(backend.get().af_fft2_r2c(c_pointer(output.arr), signal.arr, c_double_t(scale), + c_dim_t(dim0), c_dim_t(dim1))) return output def fft3_r2c(signal, dim0 = None, dim1 = None , dim2 = None, scale = None): @@ -631,8 +846,8 @@ def fft3_r2c(signal, dim0 = None, dim1 = None , dim2 = None, scale = None): scale = 1.0 output = Array() - safe_call(backend.get().af_fft3_r2c(ct.pointer(output.arr), signal.arr, ct.c_double(scale), - ct.c_longlong(dim0), ct.c_longlong(dim1), ct.c_longlong(dim2))) + safe_call(backend.get().af_fft3_r2c(c_pointer(output.arr), signal.arr, c_double_t(scale), + c_dim_t(dim0), c_dim_t(dim1), c_dim_t(dim2))) return output def _get_c2r_dim(dim, is_odd): @@ -669,7 +884,7 @@ def fft_c2r(signal, is_odd = False, scale = None): scale = 1.0/float(dim0) output = Array() - safe_call(backend.get().af_fft_c2r(ct.pointer(output.arr), signal.arr, ct.c_double(scale), is_odd)) + safe_call(backend.get().af_fft_c2r(c_pointer(output.arr), signal.arr, c_double_t(scale), is_odd)) return output def fft2_c2r(signal, is_odd = False, scale = None): @@ -704,7 +919,7 @@ def fft2_c2r(signal, is_odd = False, scale = None): scale = 1.0/float(dim0 * dim1) output = Array() - safe_call(backend.get().af_fft2_c2r(ct.pointer(output.arr), signal.arr, ct.c_double(scale), is_odd)) + safe_call(backend.get().af_fft2_c2r(c_pointer(output.arr), signal.arr, c_double_t(scale), is_odd)) return output def fft3_c2r(signal, is_odd = False, scale = None): @@ -740,7 +955,7 @@ def fft3_c2r(signal, is_odd = False, scale = None): scale = 1.0/float(dim0 * dim1 * dim2) output = Array() - safe_call(backend.get().af_fft3_c2r(ct.pointer(output.arr), signal.arr, ct.c_double(scale), is_odd)) + safe_call(backend.get().af_fft3_c2r(c_pointer(output.arr), signal.arr, c_double_t(scale), is_odd)) return output @@ -871,7 +1086,7 @@ def convolve1(signal, kernel, conv_mode = CONV_MODE.DEFAULT, conv_domain = CONV_ """ output = Array() - safe_call(backend.get().af_convolve1(ct.pointer(output.arr), signal.arr, kernel.arr, + safe_call(backend.get().af_convolve1(c_pointer(output.arr), signal.arr, kernel.arr, conv_mode.value, conv_domain.value)) return output @@ -919,10 +1134,60 @@ def convolve2(signal, kernel, conv_mode = CONV_MODE.DEFAULT, conv_domain = CONV_ """ output = Array() - safe_call(backend.get().af_convolve2(ct.pointer(output.arr), signal.arr, kernel.arr, + safe_call(backend.get().af_convolve2(c_pointer(output.arr), signal.arr, kernel.arr, conv_mode.value, conv_domain.value)) return output +def convolve2NN(signal, kernel, stride = (1, 1), padding = (0, 0), dilation = (1, 1)): + """ + This version of convolution is consistent with the machine learning + formulation that will spatially convolve a filter on 2-dimensions against a + signal. Multiple signals and filters can be batched against each other. + Furthermore, the signals and filters can be multi-dimensional however their + dimensions must match. + + Example: + Signals with dimensions: d0 x d1 x d2 x Ns + Filters with dimensions: d0 x d1 x d2 x Nf + + Resulting Convolution: d0 x d1 x Nf x Ns + + Parameters + ----------- + + signal: af.Array + - A 2 dimensional signal or batch of 2 dimensional signals. + + kernel: af.Array + - A 2 dimensional kernel or batch of 2 dimensional kernels. + + stride: tuple of ints. default: (1, 1). + - Specifies how much to stride along each dimension + + padding: tuple of ints. default: (0, 0). + - Specifies signal padding along each dimension + + dilation: tuple of ints. default: (1, 1). + - Specifies how much to dilate kernel along each dimension before convolution + + Returns + -------- + + output: af.Array + - Convolved 2D array. + + """ + output = Array() + stride_dim = dim4(stride[0], stride[1]) + padding_dim = dim4(padding[0], padding[1]) + dilation_dim = dim4(dilation[0], dilation[1]) + + safe_call(backend.get().af_convolve2_nn(c_pointer(output.arr), signal.arr, kernel.arr, + 2, c_pointer(stride_dim), + 2, c_pointer(padding_dim), + 2, c_pointer(dilation_dim))) + return output + def convolve2_separable(col_kernel, row_kernel, signal, conv_mode = CONV_MODE.DEFAULT): """ Convolution: 2D separable convolution @@ -949,7 +1214,7 @@ def convolve2_separable(col_kernel, row_kernel, signal, conv_mode = CONV_MODE.DE - Output of 2D sepearable convolution. """ output = Array() - safe_call(backend.get().af_convolve2_sep(ct.pointer(output.arr), + safe_call(backend.get().af_convolve2_sep(c_pointer(output.arr), col_kernel.arr, row_kernel.arr,signal.arr, conv_mode.value)) return output @@ -996,7 +1261,7 @@ def convolve3(signal, kernel, conv_mode = CONV_MODE.DEFAULT, conv_domain = CONV_ """ output = Array() - safe_call(backend.get().af_convolve3(ct.pointer(output.arr), signal.arr, kernel.arr, + safe_call(backend.get().af_convolve3(c_pointer(output.arr), signal.arr, kernel.arr, conv_mode.value, conv_domain.value)) return output @@ -1083,7 +1348,7 @@ def fft_convolve1(signal, kernel, conv_mode = CONV_MODE.DEFAULT): """ output = Array() - safe_call(backend.get().af_fft_convolve1(ct.pointer(output.arr), signal.arr, kernel.arr, + safe_call(backend.get().af_fft_convolve1(c_pointer(output.arr), signal.arr, kernel.arr, conv_mode.value)) return output @@ -1127,7 +1392,7 @@ def fft_convolve2(signal, kernel, conv_mode = CONV_MODE.DEFAULT): """ output = Array() - safe_call(backend.get().af_fft_convolve2(ct.pointer(output.arr), signal.arr, kernel.arr, + safe_call(backend.get().af_fft_convolve2(c_pointer(output.arr), signal.arr, kernel.arr, conv_mode.value)) return output @@ -1169,7 +1434,7 @@ def fft_convolve3(signal, kernel, conv_mode = CONV_MODE.DEFAULT): """ output = Array() - safe_call(backend.get().af_fft_convolve3(ct.pointer(output.arr), signal.arr, kernel.arr, + safe_call(backend.get().af_fft_convolve3(c_pointer(output.arr), signal.arr, kernel.arr, conv_mode.value)) return output @@ -1235,7 +1500,7 @@ def fir(B, X): """ Y = Array() - safe_call(backend.get().af_fir(ct.pointer(Y.arr), B.arr, X.arr)) + safe_call(backend.get().af_fir(c_pointer(Y.arr), B.arr, X.arr)) return Y def iir(B, A, X): @@ -1262,5 +1527,108 @@ def iir(B, A, X): """ Y = Array() - safe_call(backend.get().af_iir(ct.pointer(Y.arr), B.arr, A.arr, X.arr)) + safe_call(backend.get().af_iir(c_pointer(Y.arr), B.arr, A.arr, X.arr)) return Y + +def medfilt(signal, w0 = 3, w1 = 3, edge_pad = PAD.ZERO): + """ + Apply median filter for the signal. + + Parameters + ---------- + signal : af.Array + - A 2 D arrayfire array representing a signal, or + - A multi dimensional array representing batch of signals. + + w0 : optional: int. default: 3. + - The length of the filter along the first dimension. + + w1 : optional: int. default: 3. + - The length of the filter along the second dimension. + + edge_pad : optional: af.PAD. default: af.PAD.ZERO + - Flag specifying how the median at the edge should be treated. + + Returns + --------- + + output : af.Array + - The signal after median filter is applied. + + """ + output = Array() + safe_call(backend.get().af_medfilt(c_pointer(output.arr), + signal.arr, c_dim_t(w0), + c_dim_t(w1), edge_pad.value)) + return output + +def medfilt1(signal, length = 3, edge_pad = PAD.ZERO): + """ + Apply median filter for the signal. + + Parameters + ---------- + signal : af.Array + - A 1 D arrayfire array representing a signal, or + - A multi dimensional array representing batch of signals. + + length : optional: int. default: 3. + - The length of the filter. + + edge_pad : optional: af.PAD. default: af.PAD.ZERO + - Flag specifying how the median at the edge should be treated. + + Returns + --------- + + output : af.Array + - The signal after median filter is applied. + + """ + output = Array() + safe_call(backend.get().af_medfilt1(c_pointer(output.arr), signal.arr, c_dim_t(length), edge_pad.value)) + return output + +def medfilt2(signal, w0 = 3, w1 = 3, edge_pad = PAD.ZERO): + """ + Apply median filter for the signal. + + Parameters + ---------- + signal : af.Array + - A 2 D arrayfire array representing a signal, or + - A multi dimensional array representing batch of signals. + + w0 : optional: int. default: 3. + - The length of the filter along the first dimension. + + w1 : optional: int. default: 3. + - The length of the filter along the second dimension. + + edge_pad : optional: af.PAD. default: af.PAD.ZERO + - Flag specifying how the median at the edge should be treated. + + Returns + --------- + + output : af.Array + - The signal after median filter is applied. + + """ + output = Array() + safe_call(backend.get().af_medfilt2(c_pointer(output.arr), + signal.arr, c_dim_t(w0), + c_dim_t(w1), edge_pad.value)) + return output + +def set_fft_plan_cache_size(cache_size): + """ + Sets plan cache size. + + Parameters + ---------- + + cache_size : scalar + the number of plans that shall be cached + """ + safe_call(backend.get().af_set_fft_plan_cache_size(c_size_t(cache_size))) diff --git a/arrayfire/sparse.py b/arrayfire/sparse.py new file mode 100644 index 000000000..91b8f7a23 --- /dev/null +++ b/arrayfire/sparse.py @@ -0,0 +1,278 @@ +####################################################### +# Copyright (c) 2015, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +""" +Functions to create and manipulate sparse matrices. +""" + +from .library import * +from .array import * +import numbers +from .interop import to_array + +__to_sparse_enum = [STORAGE.DENSE, + STORAGE.CSR, + STORAGE.CSC, + STORAGE.COO] + + +def create_sparse(values, row_idx, col_idx, nrows, ncols, storage = STORAGE.CSR): + """ + Create a sparse matrix from it's constituent parts. + + Parameters + ---------- + + values : af.Array. + - Contains the non zero elements of the sparse array. + + row_idx : af.Array. + - Contains row indices of the sparse array. + + col_idx : af.Array. + - Contains column indices of the sparse array. + + nrows : int. + - specifies the number of rows in sparse matrix. + + ncols : int. + - specifies the number of columns in sparse matrix. + + storage : optional: arrayfire.STORAGE. default: arrayfire.STORAGE.CSR. + - Can be one of arrayfire.STORAGE.CSR, arrayfire.STORAGE.COO. + + Returns + ------- + + A sparse matrix. + """ + assert(isinstance(values, Array)) + assert(isinstance(row_idx, Array)) + assert(isinstance(col_idx, Array)) + out = Array() + safe_call(backend.get().af_create_sparse_array(c_pointer(out.arr), c_dim_t(nrows), c_dim_t(ncols), + values.arr, row_idx.arr, col_idx.arr, storage.value)) + return out + +def create_sparse_from_host(values, row_idx, col_idx, nrows, ncols, storage = STORAGE.CSR): + """ + Create a sparse matrix from it's constituent parts. + + Parameters + ---------- + + values : Any datatype that can be converted to array. + - Contains the non zero elements of the sparse array. + + row_idx : Any datatype that can be converted to array. + - Contains row indices of the sparse array. + + col_idx : Any datatype that can be converted to array. + - Contains column indices of the sparse array. + + nrows : int. + - specifies the number of rows in sparse matrix. + + ncols : int. + - specifies the number of columns in sparse matrix. + + storage : optional: arrayfire.STORAGE. default: arrayfire.STORAGE.CSR. + - Can be one of arrayfire.STORAGE.CSR, arrayfire.STORAGE.COO. + + Returns + ------- + + A sparse matrix. + """ + return create_sparse(to_array(values), + to_array(row_idx).as_type(Dtype.s32), + to_array(col_idx).as_type(Dtype.s32), + nrows, ncols, storage) + +def create_sparse_from_dense(dense, storage = STORAGE.CSR): + """ + Create a sparse matrix from a dense matrix. + + Parameters + ---------- + + dense : af.Array. + - A dense matrix. + + storage : optional: arrayfire.STORAGE. default: arrayfire.STORAGE.CSR. + - Can be one of arrayfire.STORAGE.CSR, arrayfire.STORAGE.COO. + + Returns + ------- + + A sparse matrix. + """ + assert(isinstance(dense, Array)) + out = Array() + safe_call(backend.get().af_create_sparse_array_from_dense(c_pointer(out.arr), dense.arr, storage.value)) + return out + +def convert_sparse_to_dense(sparse): + """ + Create a dense matrix from a sparse matrix. + + Parameters + ---------- + + sparse : af.Array. + - A sparse matrix. + + Returns + ------- + + A dense matrix. + """ + out = Array() + safe_call(backend.get().af_sparse_to_dense(c_pointer(out.arr), sparse.arr)) + return out + +def sparse_get_info(sparse): + """ + Get the constituent arrays and storage info from a sparse matrix. + + Parameters + ---------- + + sparse : af.Array. + - A sparse matrix. + + Returns + -------- + (values, row_idx, col_idx, storage) where + values : arrayfire.Array containing non zero elements from sparse matrix + row_idx : arrayfire.Array containing the row indices + col_idx : arrayfire.Array containing the column indices + storage : sparse storage + """ + values = Array() + row_idx = Array() + col_idx = Array() + stype = c_int_t(0) + safe_call(backend.get().af_sparse_get_info(c_pointer(values.arr), c_pointer(row_idx.arr), + c_pointer(col_idx.arr), c_pointer(stype), + sparse.arr)) + return (values, row_idx, col_idx, __to_sparse_enum[stype.value]) + +def sparse_get_values(sparse): + """ + Get the non zero values from sparse matrix. + + Parameters + ---------- + + sparse : af.Array. + - A sparse matrix. + + Returns + -------- + arrayfire array containing the non zero elements. + + """ + values = Array() + safe_call(backend.get().af_sparse_get_values(c_pointer(values.arr), sparse.arr)) + return values + +def sparse_get_row_idx(sparse): + """ + Get the row indices from sparse matrix. + + Parameters + ---------- + + sparse : af.Array. + - A sparse matrix. + + Returns + -------- + arrayfire array containing the non zero elements. + + """ + row_idx = Array() + safe_call(backend.get().af_sparse_get_row_idx(c_pointer(row_idx.arr), sparse.arr)) + return row_idx + +def sparse_get_col_idx(sparse): + """ + Get the column indices from sparse matrix. + + Parameters + ---------- + + sparse : af.Array. + - A sparse matrix. + + Returns + -------- + arrayfire array containing the non zero elements. + + """ + col_idx = Array() + safe_call(backend.get().af_sparse_get_col_idx(c_pointer(col_idx.arr), sparse.arr)) + return col_idx + +def sparse_get_nnz(sparse): + """ + Get the column indices from sparse matrix. + + Parameters + ---------- + + sparse : af.Array. + - A sparse matrix. + + Returns + -------- + Number of non zero elements in the sparse matrix. + + """ + nnz = c_dim_t(0) + safe_call(backend.get().af_sparse_get_nnz(c_pointer(nnz), sparse.arr)) + return nnz.value + +def sparse_get_storage(sparse): + """ + Get the column indices from sparse matrix. + + Parameters + ---------- + + sparse : af.Array. + - A sparse matrix. + + Returns + -------- + Number of non zero elements in the sparse matrix. + + """ + storage = c_int_t(0) + safe_call(backend.get().af_sparse_get_storage(c_pointer(storage), sparse.arr)) + return __to_sparse_enum[storage.value] + +def convert_sparse(sparse, storage): + """ + Convert sparse matrix from one format to another. + + Parameters + ---------- + + storage : arrayfire.STORAGE. + + Returns + ------- + + Sparse matrix converted to the appropriate type. + """ + out = Array() + safe_call(backend.get().af_sparse_convert_to(c_pointer(out.arr), sparse.arr, storage.value)) + return out diff --git a/arrayfire/statistics.py b/arrayfire/statistics.py index 597dc68e4..158da18de 100644 --- a/arrayfire/statistics.py +++ b/arrayfire/statistics.py @@ -7,100 +7,297 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## +""" +Statistical algorithms (mean, var, stdev, etc). +""" + from .library import * from .array import * def mean(a, weights=None, dim=None): + """ + Calculate mean along a given dimension. + + Parameters + ---------- + a: af.Array + The input array. + + weights: optional: af.Array. default: None. + Array to calculate the weighted mean. Must match size of the + input array. + + dim: optional: int. default: None. + The dimension for which to obtain the mean from input data. + + Returns + ------- + output: af.Array + Array containing the mean of the input array along a given + dimension. + """ if dim is not None: out = Array() if weights is None: - safe_call(backend.get().af_mean(ct.pointer(out.arr), a.arr, ct.c_int(dim))) + safe_call(backend.get().af_mean(c_pointer(out.arr), a.arr, c_int_t(dim))) else: - safe_call(backend.get().af_mean_weighted(ct.pointer(out.arr), a.arr, weights.arr, ct.c_int(dim))) + safe_call(backend.get().af_mean_weighted(c_pointer(out.arr), a.arr, weights.arr, c_int_t(dim))) return out else: - real = ct.c_double(0) - imag = ct.c_double(0) + real = c_double_t(0) + imag = c_double_t(0) if weights is None: - safe_call(backend.get().af_mean_all(ct.pointer(real), ct.pointer(imag), a.arr)) + safe_call(backend.get().af_mean_all(c_pointer(real), c_pointer(imag), a.arr)) else: - safe_call(backend.get().af_mean_all_weighted(ct.pointer(real), ct.pointer(imag), a.arr, weights.arr)) + safe_call(backend.get().af_mean_all_weighted(c_pointer(real), c_pointer(imag), a.arr, weights.arr)) real = real.value imag = imag.value return real if imag == 0 else real + imag * 1j -def var(a, isbiased=False, weights=None, dim=None): +def var(a, bias=VARIANCE.DEFAULT, weights=None, dim=None): + """ + Calculate variance along a given dimension. + + Parameters + ---------- + a: af.Array + The input array. + + bias: optional: af.VARIANCE. default: DEFAULT. + population variance(VARIANCE.POPULATION) or sample variance(VARIANCE.SAMPLE). + This is ignored if weights are provided. + + weights: optional: af.Array. default: None. + Array to calculate for the weighted mean. Must match size of + the input array. + + dim: optional: int. default: None. + The dimension for which to obtain the variance from input data. + + Returns + ------- + output: af.Array + Array containing the variance of the input array along a given + dimension. + """ if dim is not None: out = Array() if weights is None: - safe_call(backend.get().af_var(ct.pointer(out.arr), a.arr, isbiased, ct.c_int(dim))) + safe_call(backend.get().af_var_v2(c_pointer(out.arr), a.arr, bias.value, c_int_t(dim))) else: - safe_call(backend.get().af_var_weighted(ct.pointer(out.arr), a.arr, weights.arr, ct.c_int(dim))) + safe_call(backend.get().af_var_weighted(c_pointer(out.arr), a.arr, weights.arr, c_int_t(dim))) return out else: - real = ct.c_double(0) - imag = ct.c_double(0) + real = c_double_t(0) + imag = c_double_t(0) if weights is None: - safe_call(backend.get().af_var_all(ct.pointer(real), ct.pointer(imag), a.arr, isbiased)) + safe_call(backend.get().af_var_all_v2(c_pointer(real), c_pointer(imag), a.arr, bias.value)) else: - safe_call(backend.get().af_var_all_weighted(ct.pointer(real), ct.pointer(imag), a.arr, weights.arr)) + safe_call(backend.get().af_var_all_weighted(c_pointer(real), c_pointer(imag), a.arr, weights.arr)) real = real.value imag = imag.value return real if imag == 0 else real + imag * 1j -def stdev(a, dim=None): - if dim is not None: - out = Array() - safe_call(backend.get().af_stdev(ct.pointer(out.arr), a.arr, ct.c_int(dim))) - return out - else: - real = ct.c_double(0) - imag = ct.c_double(0) - safe_call(backend.get().af_stdev_all(ct.pointer(real), ct.pointer(imag), a.arr)) - real = real.value - imag = imag.value - return real if imag == 0 else real + imag * 1j +def meanvar(a, weights=None, bias=VARIANCE.DEFAULT, dim=-1): + """ + Calculate mean and variance along a given dimension. + + Parameters + ---------- + a: af.Array + The input array. + + weights: optional: af.Array. default: None. + Array to calculate for the weighted mean. Must match size of + the input array. + + bias: optional: af.VARIANCE. default: DEFAULT. + population variance(VARIANCE.POPULATION) or + sample variance(VARIANCE.SAMPLE). + + dim: optional: int. default: -1. + The dimension for which to obtain the variance from input data. + + Returns + ------- + mean: af.Array + Array containing the mean of the input array along a given + dimension. + variance: af.Array + Array containing the variance of the input array along a given + dimension. + """ + + mean_out = Array() + var_out = Array() + + if weights is None: + weights = Array() + + safe_call(backend.get().af_meanvar(c_pointer(mean_out.arr), c_pointer(var_out.arr), + a.arr, weights.arr, bias.value, c_int_t(dim))) + + return mean_out, var_out + -def cov(a, isbiased=False, dim=None): +def stdev(a, bias=VARIANCE.DEFAULT, dim=None): + """ + Calculate standard deviation along a given dimension. + + Parameters + ---------- + a: af.Array + The input array. + + bias: optional: af.VARIANCE. default: DEFAULT. + population variance(VARIANCE.POPULATION) or sample variance(VARIANCE.SAMPLE). + This is ignored if weights are provided. + + dim: optional: int. default: None. + The dimension for which to obtain the standard deviation from + input data. + + Returns + ------- + output: af.Array + Array containing the standard deviation of the input array + along a given dimension. + """ if dim is not None: out = Array() - safe_call(backend.get().af_cov(ct.pointer(out.arr), a.arr, isbiased, ct.c_int(dim))) + safe_call(backend.get().af_stdev_v2(c_pointer(out.arr), a.arr, bias.value, + c_int_t(dim))) return out else: - real = ct.c_double(0) - imag = ct.c_double(0) - safe_call(backend.get().af_cov_all(ct.pointer(real), ct.pointer(imag), a.arr, isbiased)) + real = c_double_t(0) + imag = c_double_t(0) + safe_call(backend.get().af_stdev_all_v2(c_pointer(real), c_pointer(imag), a.arr, + bias.value)) real = real.value imag = imag.value return real if imag == 0 else real + imag * 1j +def cov(a, b, bias=VARIANCE.DEFAULT): + """ + Calculate covariance along a given dimension. + + Parameters + ---------- + a: af.Array + Input array. + + b: af.Array + Input array. + + bias: optional: af.VARIANCE. default: DEFAULT. + population variance(VARIANCE.POPULATION) or sample variance(VARIANCE.SAMPLE). + + Returns + ------- + output: af.Array + Array containing the covariance of the input array along a given dimension. + """ + out = Array() + safe_call(backend.get().af_cov_v2(c_pointer(out.arr), a.arr, b.arr, bias.value)) + return out + def median(a, dim=None): + """ + Calculate median along a given dimension. + + Parameters + ---------- + a: af.Array + The input array. + + dim: optional: int. default: None. + The dimension for which to obtain the median from input data. + + Returns + ------- + output: af.Array + Array containing the median of the input array along a + given dimension. + """ if dim is not None: out = Array() - safe_call(backend.get().af_median(ct.pointer(out.arr), a.arr, ct.c_int(dim))) + safe_call(backend.get().af_median(c_pointer(out.arr), a.arr, c_int_t(dim))) return out else: - real = ct.c_double(0) - imag = ct.c_double(0) - safe_call(backend.get().af_median_all(ct.pointer(real), ct.pointer(imag), a.arr)) + real = c_double_t(0) + imag = c_double_t(0) + safe_call(backend.get().af_median_all(c_pointer(real), c_pointer(imag), a.arr)) real = real.value imag = imag.value return real if imag == 0 else real + imag * 1j def corrcoef(x, y): - real = ct.c_double(0) - imag = ct.c_double(0) - safe_call(backend.get().af_corrcoef(ct.pointer(real), ct.pointer(imag), x.arr, y.arr)) + """ + Calculate the correlation coefficient of the input arrays. + + Parameters + ---------- + x: af.Array + The first input array. + + y: af.Array + The second input array. + + Returns + ------- + output: af.Array + Array containing the correlation coefficient of the input arrays. + """ + real = c_double_t(0) + imag = c_double_t(0) + safe_call(backend.get().af_corrcoef(c_pointer(real), c_pointer(imag), x.arr, y.arr)) real = real.value imag = imag.value return real if imag == 0 else real + imag * 1j + +def topk(data, k, dim=0, order=TOPK.DEFAULT): + """ + Return top k elements along a single dimension. + + Parameters + ---------- + + data: af.Array + Input array to return k elements from. + + k: scalar. default: 0 + The number of elements to return from input array. + + dim: optional: scalar. default: 0 + The dimension along which the top k elements are + extracted. Note: at the moment, topk() only supports the + extraction of values along the first dimension. + + order: optional: af.TOPK. default: af.TOPK.DEFAULT + The ordering of k extracted elements. Defaults to top k max values. + + Returns + ------- + + values: af.Array + Top k elements from input array. + indices: af.Array + Corresponding index array to top k elements. + """ + + values = Array() + indices = Array() + + safe_call(backend.get().af_topk(c_pointer(values.arr), c_pointer(indices.arr), data.arr, k, c_int_t(dim), order.value)) + + return values,indices diff --git a/arrayfire/timer.py b/arrayfire/timer.py index 6f6c3efc3..d523fcd93 100644 --- a/arrayfire/timer.py +++ b/arrayfire/timer.py @@ -7,7 +7,7 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## """ -Functions to time arrayfire functions +Functions to time arrayfire. """ from .library import * diff --git a/arrayfire/util.py b/arrayfire/util.py index a20002cd6..44af6000d 100644 --- a/arrayfire/util.py +++ b/arrayfire/util.py @@ -7,15 +7,19 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## +""" +Utility functions to help with Array metadata. +""" + from .library import * import numbers def dim4(d0=1, d1=1, d2=1, d3=1): - c_dim4 = ct.c_longlong * 4 + c_dim4 = c_dim_t * 4 out = c_dim4(1, 1, 1, 1) for i, dim in enumerate((d0, d1, d2, d3)): - if (dim is not None): out[i] = dim + if (dim is not None): out[i] = c_dim_t(dim) return out @@ -69,58 +73,79 @@ def to_str(c_str): def safe_call(af_error): if (af_error != ERR.NONE.value): - err_str = ct.c_char_p(0) - err_len = ct.c_longlong(0) - backend.get().af_get_last_error(ct.pointer(err_str), ct.pointer(err_len)) - raise RuntimeError(to_str(err_str), af_error) + err_str = c_char_ptr_t(0) + err_len = c_dim_t(0) + backend.get().af_get_last_error(c_pointer(err_str), c_pointer(err_len)) + raise RuntimeError(to_str(err_str)) def get_version(): - major=ct.c_int(0) - minor=ct.c_int(0) - patch=ct.c_int(0) - safe_call(backend.get().af_get_version(ct.pointer(major), ct.pointer(minor), ct.pointer(patch))) + """ + Function to get the version of arrayfire. + """ + major=c_int_t(0) + minor=c_int_t(0) + patch=c_int_t(0) + safe_call(backend.get().af_get_version(c_pointer(major), c_pointer(minor), c_pointer(patch))) return major.value,minor.value,patch.value +def get_reversion(): + """ + Function to get the revision hash of the library. + """ + return to_str(backend.get().af_get_revision()) + to_dtype = {'f' : Dtype.f32, 'd' : Dtype.f64, 'b' : Dtype.b8, 'B' : Dtype.u8, + 'h' : Dtype.s16, + 'H' : Dtype.u16, 'i' : Dtype.s32, 'I' : Dtype.u32, 'l' : Dtype.s64, 'L' : Dtype.u64, 'F' : Dtype.c32, - 'D' : Dtype.c64} + 'D' : Dtype.c64, + 'hf': Dtype.f16} to_typecode = {Dtype.f32.value : 'f', Dtype.f64.value : 'd', Dtype.b8.value : 'b', Dtype.u8.value : 'B', + Dtype.s16.value : 'h', + Dtype.u16.value : 'H', Dtype.s32.value : 'i', Dtype.u32.value : 'I', Dtype.s64.value : 'l', Dtype.u64.value : 'L', Dtype.c32.value : 'F', - Dtype.c64.value : 'D'} - -to_c_type = {Dtype.f32.value : ct.c_float, - Dtype.f64.value : ct.c_double, - Dtype.b8.value : ct.c_char, - Dtype.u8.value : ct.c_ubyte, - Dtype.s32.value : ct.c_int, - Dtype.u32.value : ct.c_uint, - Dtype.s64.value : ct.c_longlong, - Dtype.u64.value : ct.c_ulonglong, - Dtype.c32.value : ct.c_float * 2, - Dtype.c64.value : ct.c_double * 2} + Dtype.c64.value : 'D', + Dtype.f16.value : 'hf'} + +to_c_type = {Dtype.f32.value : c_float_t, + Dtype.f64.value : c_double_t, + Dtype.b8.value : c_char_t, + Dtype.u8.value : c_uchar_t, + Dtype.s16.value : c_short_t, + Dtype.u16.value : c_ushort_t, + Dtype.s32.value : c_int_t, + Dtype.u32.value : c_uint_t, + Dtype.s64.value : c_longlong_t, + Dtype.u64.value : c_ulonglong_t, + Dtype.c32.value : c_float_t * 2, + Dtype.c64.value : c_double_t * 2, + Dtype.f16.value : c_ushort_t} to_typename = {Dtype.f32.value : 'float', Dtype.f64.value : 'double', Dtype.b8.value : 'bool', Dtype.u8.value : 'unsigned char', + Dtype.s16.value : 'short int', + Dtype.u16.value : 'unsigned short int', Dtype.s32.value : 'int', Dtype.u32.value : 'unsigned int', Dtype.s64.value : 'long int', Dtype.u64.value : 'unsigned long int', Dtype.c32.value : 'float complex', - Dtype.c64.value : 'double complex'} + Dtype.c64.value : 'double complex', + Dtype.f16.value : 'half'} diff --git a/arrayfire/vision.py b/arrayfire/vision.py index e605865cf..49cdc8fd8 100644 --- a/arrayfire/vision.py +++ b/arrayfire/vision.py @@ -8,7 +8,7 @@ ######################################################## """ -Computer vision functions for arrayfire. +Computer vision functions (FAST, ORB, etc) """ from .library import * @@ -47,9 +47,9 @@ def fast(image, threshold=20.0, arc_length=9, non_max=True, feature_ratio=0.05, """ out = Features() - safe_call(backend.get().af_fast(ct.pointer(out.feat), - image.arr, ct.c_float(threshold), ct.c_uint(arc_length), non_max, - ct.c_float(feature_ratio), ct.c_uint(edge))) + safe_call(backend.get().af_fast(c_pointer(out.feat), + image.arr, c_float_t(threshold), c_uint_t(arc_length), non_max, + c_float_t(feature_ratio), c_uint_t(edge))) return out def harris(image, max_corners=500, min_response=1E5, sigma=1.0, block_size=0, k_thr=0.04): @@ -91,9 +91,9 @@ def harris(image, max_corners=500, min_response=1E5, sigma=1.0, block_size=0, k_ """ out = Features() - safe_call(backend.get().af_harris(ct.pointer(out.feat), - image.arr, ct.c_uint(max_corners), ct.c_float(min_response), - ct.c_float(sigma), ct.c_uint(block_size), ct.c_float(k_thr))) + safe_call(backend.get().af_harris(c_pointer(out.feat), + image.arr, c_uint_t(max_corners), c_float_t(min_response), + c_float_t(sigma), c_uint_t(block_size), c_float_t(k_thr))) return out def orb(image, threshold=20.0, max_features=400, scale = 1.5, num_levels = 4, blur_image = False): @@ -131,9 +131,9 @@ def orb(image, threshold=20.0, max_features=400, scale = 1.5, num_levels = 4, bl """ feat = Features() desc = Array() - safe_call(backend.get().af_orb(ct.pointer(feat.feat), ct.pointer(desc.arr), - ct.c_float(threshold), ct.c_uint(max_features), - ct.c_float(scale), ct.c_uint(num_levels), blur_image)) + safe_call(backend.get().af_orb(c_pointer(feat.feat), c_pointer(desc.arr), image.arr, + c_float_t(threshold), c_uint_t(max_features), + c_float_t(scale), c_uint_t(num_levels), blur_image)) return feat, desc def hamming_matcher(query, database, dim = 0, num_nearest = 1): @@ -164,9 +164,9 @@ def hamming_matcher(query, database, dim = 0, num_nearest = 1): """ index = Array() dist = Array() - safe_call(backend.get().af_hamming_matcher(ct.pointer(idx.arr), ct.pointer(dist.arr), + safe_call(backend.get().af_hamming_matcher(c_pointer(index.arr), c_pointer(dist.arr), query.arr, database.arr, - ct.c_longlong(dim), ct.c_longlong(num_nearest))) + c_dim_t(dim), c_dim_t(num_nearest))) return index, dist def nearest_neighbour(query, database, dim = 0, num_nearest = 1, match_type=MATCH.SSD): @@ -200,9 +200,9 @@ def nearest_neighbour(query, database, dim = 0, num_nearest = 1, match_type=MATC """ index = Array() dist = Array() - safe_call(backend.get().af_nearest_neighbour(ct.pointer(idx.arr), ct.pointer(dist.arr), + safe_call(backend.get().af_nearest_neighbour(c_pointer(index.arr), c_pointer(dist.arr), query.arr, database.arr, - ct.c_longlong(dim), ct.c_longlong(num_nearest), + c_dim_t(dim), c_dim_t(num_nearest), match_type.value)) return index, dist @@ -229,7 +229,7 @@ def match_template(image, template, match_type = MATCH.SAD): """ out = Array() - safe_call(backend.get().af_match_template(ct.pointer(out.arr), + safe_call(backend.get().af_match_template(c_pointer(out.arr), image.arr, template.arr, match_type.value)) return out @@ -266,10 +266,10 @@ def susan(image, radius=3, diff_thr=32, geom_thr=10, feature_ratio=0.05, edge=3) """ out = Features() - safe_call(backend.get().af_susan(ct.pointer(out.feat), - image.arr, ct.c_uint(radius), ct.c_float(diff_thr), - ct.c_float(geom_thr), ct.c_float(feature_ratio), - ct.c_uint(edge))) + safe_call(backend.get().af_susan(c_pointer(out.feat), + image.arr, c_uint_t(radius), c_float_t(diff_thr), + c_float_t(geom_thr), c_float_t(feature_ratio), + c_uint_t(edge))) return out def dog(image, radius1, radius2): @@ -301,7 +301,7 @@ def dog(image, radius1, radius2): """ out = Array() - safe_call(backend.get().af_dog(ct.pointer(out.arr), + safe_call(backend.get().af_dog(c_pointer(out.arr), image.arr, radius1, radius2)) return out @@ -345,9 +345,9 @@ def sift(image, num_layers=3, contrast_threshold=0.04, edge_threshold=10.0, init feat = Features() desc = Array() - safe_call(af_sift(ct.pointer(feat), ct.pointer(desc), - image.arr, num_layers, contrast_threshold, edge_threshold, - initial_sigma, double_input, intensity_scale, feature_ratio)) + safe_call(backend.get().af_sift(c_pointer(feat.feat), c_pointer(desc.arr), + image.arr, num_layers, c_float_t(contrast_threshold), c_float_t(edge_threshold), + c_float_t(initial_sigma), double_input, c_float_t(intensity_scale), c_float_t(feature_ratio))) return (feat, desc) @@ -391,9 +391,11 @@ def gloh(image, num_layers=3, contrast_threshold=0.04, edge_threshold=10.0, init feat = Features() desc = Array() - safe_call(af_gloh(ct.pointer(feat), ct.pointer(desc), - image.arr, num_layers, contrast_threshold, edge_threshold, - initial_sigma, double_input, intensity_scale, feature_ratio)) + safe_call(backend.get().af_gloh(c_pointer(feat.feat), c_pointer(desc.arr), + image.arr, num_layers, c_float_t(contrast_threshold), + c_float_t(edge_threshold), c_float_t(initial_sigma), + double_input, c_float_t(intensity_scale), + c_float_t(feature_ratio))) return (feat, desc) @@ -433,8 +435,8 @@ def homography(x_src, y_src, x_dst, y_dst, htype = HOMOGRAPHY.RANSAC, """ H = Array() - inliers = ct.c_int(0) - safe_call(backend.get().af_homography(ct.pointer(H), ct.pointer(inliers), + inliers = c_int_t(0) + safe_call(backend.get().af_homography(c_pointer(H), c_pointer(inliers), x_src.arr, y_src.arr, x_dst.arr, y_dst.arr, htype.value, ransac_threshold, iters, out_type.value)) return (H, inliers) diff --git a/assets b/assets new file mode 160000 index 000000000..cd08d7496 --- /dev/null +++ b/assets @@ -0,0 +1 @@ +Subproject commit cd08d749611b324012555ad6f23fd76c5465bd6c diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..45690fe35 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,225 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " epub3 to make an epub3" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + @echo " dummy to check syntax errors of document sources" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ArrayFire.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ArrayFire.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/ArrayFire" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ArrayFire" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: epub3 +epub3: + $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 + @echo + @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +.PHONY: dummy +dummy: + $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy + @echo + @echo "Build finished. Dummy builder generates no files." diff --git a/docs/_static/style.css b/docs/_static/style.css new file mode 100644 index 000000000..bef0f094c --- /dev/null +++ b/docs/_static/style.css @@ -0,0 +1,3 @@ +dl.function { + border-top-style: solid; +} diff --git a/docs/_templates/page.html b/docs/_templates/page.html new file mode 100644 index 000000000..fb7091880 --- /dev/null +++ b/docs/_templates/page.html @@ -0,0 +1,3 @@ +{% extends "!page.html" %} + +{% set css_files = css_files + ["_static/style.css"] %} diff --git a/docs/arrayfire.algorithm.rst b/docs/arrayfire.algorithm.rst new file mode 100644 index 000000000..e41092935 --- /dev/null +++ b/docs/arrayfire.algorithm.rst @@ -0,0 +1,7 @@ +arrayfire.algorithm module +========================== + +.. automodule:: arrayfire.algorithm + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.arith.rst b/docs/arrayfire.arith.rst new file mode 100644 index 000000000..d874a2ecd --- /dev/null +++ b/docs/arrayfire.arith.rst @@ -0,0 +1,7 @@ +arrayfire.arith module +====================== + +.. automodule:: arrayfire.arith + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.array.rst b/docs/arrayfire.array.rst new file mode 100644 index 000000000..84e335108 --- /dev/null +++ b/docs/arrayfire.array.rst @@ -0,0 +1,7 @@ +arrayfire.array module +====================== + +.. automodule:: arrayfire.array + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.base.rst b/docs/arrayfire.base.rst new file mode 100644 index 000000000..be1e420aa --- /dev/null +++ b/docs/arrayfire.base.rst @@ -0,0 +1,7 @@ +arrayfire.base module +===================== + +.. automodule:: arrayfire.base + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.bcast.rst b/docs/arrayfire.bcast.rst new file mode 100644 index 000000000..0bcd4e521 --- /dev/null +++ b/docs/arrayfire.bcast.rst @@ -0,0 +1,7 @@ +arrayfire.bcast module +====================== + +.. automodule:: arrayfire.bcast + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.blas.rst b/docs/arrayfire.blas.rst new file mode 100644 index 000000000..054f98fc5 --- /dev/null +++ b/docs/arrayfire.blas.rst @@ -0,0 +1,7 @@ +arrayfire.blas module +===================== + +.. automodule:: arrayfire.blas + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.cuda.rst b/docs/arrayfire.cuda.rst new file mode 100644 index 000000000..f0f3a48b9 --- /dev/null +++ b/docs/arrayfire.cuda.rst @@ -0,0 +1,7 @@ +arrayfire.cuda module +===================== + +.. automodule:: arrayfire.cuda + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.data.rst b/docs/arrayfire.data.rst new file mode 100644 index 000000000..de208ff1d --- /dev/null +++ b/docs/arrayfire.data.rst @@ -0,0 +1,7 @@ +arrayfire.data module +===================== + +.. automodule:: arrayfire.data + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.device.rst b/docs/arrayfire.device.rst new file mode 100644 index 000000000..ad98bf639 --- /dev/null +++ b/docs/arrayfire.device.rst @@ -0,0 +1,7 @@ +arrayfire.device module +======================= + +.. automodule:: arrayfire.device + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.features.rst b/docs/arrayfire.features.rst new file mode 100644 index 000000000..44629c6f4 --- /dev/null +++ b/docs/arrayfire.features.rst @@ -0,0 +1,7 @@ +arrayfire.features module +========================= + +.. automodule:: arrayfire.features + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.graphics.rst b/docs/arrayfire.graphics.rst new file mode 100644 index 000000000..3a1505eae --- /dev/null +++ b/docs/arrayfire.graphics.rst @@ -0,0 +1,7 @@ +arrayfire.graphics module +========================= + +.. automodule:: arrayfire.graphics + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.image.rst b/docs/arrayfire.image.rst new file mode 100644 index 000000000..d4934b44b --- /dev/null +++ b/docs/arrayfire.image.rst @@ -0,0 +1,7 @@ +arrayfire.image module +====================== + +.. automodule:: arrayfire.image + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.index.rst b/docs/arrayfire.index.rst new file mode 100644 index 000000000..95c3dcde1 --- /dev/null +++ b/docs/arrayfire.index.rst @@ -0,0 +1,7 @@ +arrayfire.index module +====================== + +.. automodule:: arrayfire.index + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.interop.rst b/docs/arrayfire.interop.rst new file mode 100644 index 000000000..f7886aa07 --- /dev/null +++ b/docs/arrayfire.interop.rst @@ -0,0 +1,7 @@ +arrayfire.interop module +======================== + +.. automodule:: arrayfire.interop + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.lapack.rst b/docs/arrayfire.lapack.rst new file mode 100644 index 000000000..4f14e4c24 --- /dev/null +++ b/docs/arrayfire.lapack.rst @@ -0,0 +1,7 @@ +arrayfire.lapack module +======================= + +.. automodule:: arrayfire.lapack + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.library.rst b/docs/arrayfire.library.rst new file mode 100644 index 000000000..6c9c78d10 --- /dev/null +++ b/docs/arrayfire.library.rst @@ -0,0 +1,7 @@ +arrayfire.library module +======================== + +.. automodule:: arrayfire.library + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.ml.rst b/docs/arrayfire.ml.rst new file mode 100644 index 000000000..0c53c1719 --- /dev/null +++ b/docs/arrayfire.ml.rst @@ -0,0 +1,7 @@ +arrayfire.ml module +======================= + +.. automodule:: arrayfire.ml + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.opencl.rst b/docs/arrayfire.opencl.rst new file mode 100644 index 000000000..b4622b38d --- /dev/null +++ b/docs/arrayfire.opencl.rst @@ -0,0 +1,7 @@ +arrayfire.opencl module +======================= + +.. automodule:: arrayfire.opencl + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.random.rst b/docs/arrayfire.random.rst new file mode 100644 index 000000000..33c86f96b --- /dev/null +++ b/docs/arrayfire.random.rst @@ -0,0 +1,7 @@ +arrayfire.random module +========================= + +.. automodule:: arrayfire.random + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.rst b/docs/arrayfire.rst new file mode 100644 index 000000000..7504fd82b --- /dev/null +++ b/docs/arrayfire.rst @@ -0,0 +1,37 @@ +ArrayFire Python Wrapper +======================== + +Introduction +--------------- + +.. automodule:: arrayfire + +Submodules +---------- + +.. autosummary:: + arrayfire.algorithm + arrayfire.arith + arrayfire.array + arrayfire.base + arrayfire.bcast + arrayfire.blas + arrayfire.cuda + arrayfire.data + arrayfire.device + arrayfire.features + arrayfire.graphics + arrayfire.image + arrayfire.index + arrayfire.interop + arrayfire.lapack + arrayfire.library + arrayfire.ml + arrayfire.opencl + arrayfire.random + arrayfire.sparse + arrayfire.signal + arrayfire.statistics + arrayfire.timer + arrayfire.util + arrayfire.vision diff --git a/docs/arrayfire.signal.rst b/docs/arrayfire.signal.rst new file mode 100644 index 000000000..ae06fdf9e --- /dev/null +++ b/docs/arrayfire.signal.rst @@ -0,0 +1,7 @@ +arrayfire.signal module +======================= + +.. automodule:: arrayfire.signal + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.sparse.rst b/docs/arrayfire.sparse.rst new file mode 100644 index 000000000..cc087885d --- /dev/null +++ b/docs/arrayfire.sparse.rst @@ -0,0 +1,7 @@ +arrayfire.sparse module +========================= + +.. automodule:: arrayfire.sparse + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.statistics.rst b/docs/arrayfire.statistics.rst new file mode 100644 index 000000000..a20ef21a2 --- /dev/null +++ b/docs/arrayfire.statistics.rst @@ -0,0 +1,7 @@ +arrayfire.statistics module +=========================== + +.. automodule:: arrayfire.statistics + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.timer.rst b/docs/arrayfire.timer.rst new file mode 100644 index 000000000..cb9cac2ed --- /dev/null +++ b/docs/arrayfire.timer.rst @@ -0,0 +1,7 @@ +arrayfire.timer module +====================== + +.. automodule:: arrayfire.timer + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.util.rst b/docs/arrayfire.util.rst new file mode 100644 index 000000000..c5192aeb8 --- /dev/null +++ b/docs/arrayfire.util.rst @@ -0,0 +1,7 @@ +arrayfire.util module +===================== + +.. automodule:: arrayfire.util + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire.vision.rst b/docs/arrayfire.vision.rst new file mode 100644 index 000000000..d2f229a08 --- /dev/null +++ b/docs/arrayfire.vision.rst @@ -0,0 +1,7 @@ +arrayfire.vision module +======================= + +.. automodule:: arrayfire.vision + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/arrayfire_logo_symbol.png b/docs/arrayfire_logo_symbol.png new file mode 100644 index 000000000..7a2476d11 Binary files /dev/null and b/docs/arrayfire_logo_symbol.png differ diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..44374afb1 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,341 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# ArrayFire documentation build configuration file, created by +# sphinx-quickstart on Fri Jun 24 20:20:43 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('..')) +from __af_version__ import full_version + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.mathjax', + 'sphinx.ext.viewcode', + 'numpydoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.doctest', + 'sphinx.ext.inheritance_diagram'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +# +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'ArrayFire' +copyright = '2020, ArrayFire' +author = 'Stefan Yurkevitch, Pradeep Garigipati, Umar Arshad' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = full_version +# The full version, including alpha/beta/rc tags. +release = full_version + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +html_title = 'ArrayFire Python documentation' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +html_logo = "arrayfire_logo_symbol.png" + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +# html_domain_indices = True + +# If false, no index is generated. +# +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ArrayFiredoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'ArrayFire.tex', 'ArrayFire Documentation', + 'Pavan Yalamanchili', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# +# latex_use_parts = False + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'arrayfire', 'ArrayFire Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'ArrayFire', 'ArrayFire Documentation', + author, 'ArrayFire', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False diff --git a/docs/index.rst b/docs/index.rst new file mode 120000 index 000000000..4c689ce14 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1 @@ +arrayfire.rst \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 000000000..1b46d26d8 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,281 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. epub3 to make an epub3 + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + echo. dummy to check syntax errors of document sources + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ArrayFire.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ArrayFire.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "epub3" ( + %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +if "%1" == "dummy" ( + %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. Dummy builder generates no files. + goto end +) + +:end diff --git a/docs/modules.rst b/docs/modules.rst new file mode 100644 index 000000000..c5f5de84d --- /dev/null +++ b/docs/modules.rst @@ -0,0 +1,7 @@ +arrayfire +========= + +.. toctree:: + :maxdepth: 4 + + arrayfire diff --git a/examples/benchmarks/bench_blas.py b/examples/benchmarks/bench_blas.py index 1b315900d..21b776869 100644 --- a/examples/benchmarks/bench_blas.py +++ b/examples/benchmarks/bench_blas.py @@ -12,15 +12,49 @@ import sys from time import time -from arrayfire import (array, randu, matmul) import arrayfire as af -def bench(A, iters = 100): - start = time() - for t in range(iters): - B = af.matmul(A, A) +try: + import numpy as np +except ImportError: + np = None + + +def calc_arrayfire(n): + A = af.randu(n, n) af.sync() - return (time() - start) / iters + + def run(iters): + for t in range(iters): + B = af.matmul(A, A) + af.sync() + + return run + + +def calc_numpy(n): + np.random.seed(1) + A = np.random.rand(n, n).astype(np.float32) + + def run(iters): + for t in range(iters): + B = np.dot(A, A) + + return run + + +def bench(calc, iters=100, upto=2048): + _, name = calc.__name__.split("_") + print("Benchmark N x N matrix multiply on %s" % name) + + for n in range(128, upto + 128, 128): + run = calc(n) + start = time() + run(iters) + t = (time() - start) / iters + gflops = 2.0 * (n ** 3) / (t * 1E9) + print("Time taken for %4d x %4d: %0.4f Gflops" % (n, n, gflops)) + if __name__ == "__main__": @@ -28,12 +62,7 @@ def bench(A, iters = 100): af.set_device(int(sys.argv[1])) af.info() - print("Benchmark N x N matrix multiply") - - for n in range(128, 2048 + 128, 128): - A = af.randu(n, n) - af.sync() - t = bench(A) - gflops = 2.0 * (n**3) / (t * 1E9) - print("Time taken for %4d x %4d: %0.4f Gflops" % (n, n, gflops)) + bench(calc_arrayfire) + if np: + bench(calc_numpy, upto=512) diff --git a/examples/benchmarks/bench_cg.py b/examples/benchmarks/bench_cg.py new file mode 100644 index 000000000..33c5b856c --- /dev/null +++ b/examples/benchmarks/bench_cg.py @@ -0,0 +1,202 @@ +#!/usr/bin/python + +####################################################### +# Copyright (c) 2015, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + + +import sys +from time import time +import arrayfire as af + +try: + import numpy as np +except ImportError: + np = None + +try: + from scipy import sparse as sp + from scipy.sparse import linalg +except ImportError: + sp = None + + +def to_numpy(A): + return np.asarray(A.to_list(), dtype=np.float32) + + +def to_sparse(A): + return af.sparse.create_sparse_from_dense(A) + + +def to_scipy_sparse(spA, fmt='csr'): + vals = np.asarray(af.sparse.sparse_get_values(spA).to_list(), + dtype = np.float32) + rows = np.asarray(af.sparse.sparse_get_row_idx(spA).to_list(), + dtype = np.int) + cols = np.asarray(af.sparse.sparse_get_col_idx(spA).to_list(), + dtype = np.int) + return sp.csr_matrix((vals, cols, rows), dtype=np.float32) + + +def setup_input(n, sparsity=7): + T = af.randu(n, n, dtype=af.Dtype.f32) + A = af.floor(T*1000) + A = A * ((A % sparsity) == 0) / 1000 + A = A.T + A + n*af.identity(n, n, dtype=af.Dtype.f32) + x0 = af.randu(n, dtype=af.Dtype.f32) + b = af.matmul(A, x0) + # printing + # nnz = af.sum((A != 0)) + # print "Sparsity of A: %2.2f %%" %(100*nnz/n**2,) + return A, b, x0 + + +def input_info(A, Asp): + m, n = A.dims() + nnz = af.sum((A != 0)) + print(" matrix size: %i x %i" %(m, n)) + print(" matrix sparsity: %2.2f %%" %(100*nnz/n**2,)) + print(" dense matrix memory usage: ") + print(" sparse matrix memory usage: ") + + +def calc_arrayfire(A, b, x0, maxiter=10): + x = af.constant(0, b.dims()[0], dtype=af.Dtype.f32) + r = b - af.matmul(A, x) + p = r + for i in range(maxiter): + Ap = af.matmul(A, p) + alpha_num = af.dot(r, r) + alpha_den = af.dot(p, Ap) + alpha = alpha_num/alpha_den + r -= af.tile(alpha, Ap.dims()[0]) * Ap + x += af.tile(alpha, Ap.dims()[0]) * p + beta_num = af.dot(r, r) + beta = beta_num/alpha_num + p = r + af.tile(beta, p.dims()[0]) * p + af.eval(x) + res = x0 - x + return x, af.dot(res, res) + + +def calc_numpy(A, b, x0, maxiter=10): + x = np.zeros(len(b), dtype=np.float32) + r = b - np.dot(A, x) + p = r.copy() + for i in range(maxiter): + Ap = np.dot(A, p) + alpha_num = np.dot(r, r) + alpha_den = np.dot(p, Ap) + alpha = alpha_num/alpha_den + r -= alpha * Ap + x += alpha * p + beta_num = np.dot(r, r) + beta = beta_num/alpha_num + p = r + beta * p + res = x0 - x + return x, np.dot(res, res) + + +def calc_scipy_sparse(A, b, x0, maxiter=10): + x = np.zeros(len(b), dtype=np.float32) + r = b - A*x + p = r.copy() + for i in range(maxiter): + Ap = A*p + alpha_num = np.dot(r, r) + alpha_den = np.dot(p, Ap) + alpha = alpha_num/alpha_den + r -= alpha * Ap + x += alpha * p + beta_num = np.dot(r, r) + beta = beta_num/alpha_num + p = r + beta * p + res = x0 - x + return x, np.dot(res, res) + + +def calc_scipy_sparse_linalg_cg(A, b, x0, maxiter=10): + x = np.zeros(len(b), dtype=np.float32) + x, _ = linalg.cg(A, b, x, tol=0., maxiter=maxiter) + res = x0 - x + return x, np.dot(res, res) + + +def timeit(calc, iters, args): + t0 = time() + for i in range(iters): + calc(*args) + dt = time() - t0 + return 1000*dt/iters # ms + + +def test(): + print("\nTesting benchmark functions...") + A, b, x0 = setup_input(n=50, sparsity=7) # dense A + Asp = to_sparse(A) + x1, _ = calc_arrayfire(A, b, x0) + x2, _ = calc_arrayfire(Asp, b, x0) + if af.sum(af.abs(x1 - x2)/x2 > 1e-5): + raise ValueError("arrayfire test failed") + if np: + An = to_numpy(A) + bn = to_numpy(b) + x0n = to_numpy(x0) + x3, _ = calc_numpy(An, bn, x0n) + if not np.allclose(x3, x1.to_list()): + raise ValueError("numpy test failed") + if sp: + Asc = to_scipy_sparse(Asp) + x4, _ = calc_scipy_sparse(Asc, bn, x0n) + if not np.allclose(x4, x1.to_list()): + raise ValueError("scipy.sparse test failed") + x5, _ = calc_scipy_sparse_linalg_cg(Asc, bn, x0n) + if not np.allclose(x5, x1.to_list()): + raise ValueError("scipy.sparse.linalg.cg test failed") + print(" all tests passed...") + + +def bench(n=4*1024, sparsity=7, maxiter=10, iters=10): + + # generate data + print("\nGenerating benchmark data for n = %i ..." %n) + A, b, x0 = setup_input(n, sparsity) # dense A + Asp = to_sparse(A) # sparse A + input_info(A, Asp) + + # make benchmarks + print("Benchmarking CG solver for n = %i ..." %n) + t1 = timeit(calc_arrayfire, iters, args=(A, b, x0, maxiter)) + print(" arrayfire - dense: %f ms" %t1) + t2 = timeit(calc_arrayfire, iters, args=(Asp, b, x0, maxiter)) + print(" arrayfire - sparse: %f ms" %t2) + if np: + An = to_numpy(A) + bn = to_numpy(b) + x0n = to_numpy(x0) + t3 = timeit(calc_numpy, iters, args=(An, bn, x0n, maxiter)) + print(" numpy - dense: %f ms" %t3) + if sp: + Asc = to_scipy_sparse(Asp) + t4 = timeit(calc_scipy_sparse, iters, args=(Asc, bn, x0n, maxiter)) + print(" scipy - sparse: %f ms" %t4) + t5 = timeit(calc_scipy_sparse_linalg_cg, iters, args=(Asc, bn, x0n, maxiter)) + print(" scipy - sparse.linalg.cg: %f ms" %t5) + +if __name__ == "__main__": + #af.set_backend('cpu', unsafe=True) + + if (len(sys.argv) > 1): + af.set_device(int(sys.argv[1])) + + af.info() + test() + + for n in (128, 256, 512, 1024, 2048, 4096): + bench(n) diff --git a/examples/benchmarks/bench_fft.py b/examples/benchmarks/bench_fft.py index 735e8d6ed..4d9a3d7b1 100644 --- a/examples/benchmarks/bench_fft.py +++ b/examples/benchmarks/bench_fft.py @@ -12,15 +12,51 @@ import sys from time import time -from arrayfire import (array, randu, matmul) import arrayfire as af -def bench(A, iters = 100): - start = time() - for t in range(iters): - B = af.fft2(A) +try: + import numpy as np +except ImportError: + np = None + + +def calc_arrayfire(n): + A = af.randu(n, n) af.sync() - return (time() - start) / iters + + def run(iters): + for t in range(iters): + B = af.fft2(A) + + af.sync() + + return run + + +def calc_numpy(n): + np.random.seed(1) + A = np.random.rand(n, n).astype(np.float32) + + def run(iters): + for t in range(iters): + B = np.fft.fft2(A) + + return run + + +def bench(calc, iters=100, upto=13): + _, name = calc.__name__.split("_") + print("Benchmark N x N 2D fft on %s" % name) + + for M in range(7, upto): + N = 1 << M + run = calc(N) + start = time() + run(iters) + t = (time() - start) / iters + gflops = (10.0 * N * N * M) / (t * 1E9) + print("Time taken for %4d x %4d: %0.4f Gflops" % (N, N, gflops)) + if __name__ == "__main__": @@ -28,13 +64,7 @@ def bench(A, iters = 100): af.set_device(int(sys.argv[1])) af.info() - print("Benchmark N x N 2D fft") - for M in range(7, 13): - N = 1 << M - A = af.randu(N, N) - af.sync() - - t = bench(A) - gflops = (10.0 * N * N * M) / (t * 1E9) - print("Time taken for %4d x %4d: %0.4f Gflops" % (N, N, gflops)) + bench(calc_arrayfire) + if np: + bench(calc_numpy, upto=10) diff --git a/examples/benchmarks/monte_carlo_pi.py b/examples/benchmarks/monte_carlo_pi.py index fc35b3d4a..440c01594 100755 --- a/examples/benchmarks/monte_carlo_pi.py +++ b/examples/benchmarks/monte_carlo_pi.py @@ -11,26 +11,35 @@ from random import random from time import time -from arrayfire import (array, randu) import arrayfire as af import sys +try: + import numpy as np +except ImportError: + np = None + #alias range / xrange because xrange is faster than range in python2 try: frange = xrange #Python2 except NameError: frange = range #Python3 - -def calc_pi_device(samples): - x = randu(samples) - y = randu(samples) - return 4 * af.sum((x * x + y * y) < 1) / samples - # Having the function outside is faster than the lambda inside def in_circle(x, y): return (x*x + y*y) < 1 +def calc_pi_device(samples): + x = af.randu(samples) + y = af.randu(samples) + return 4 * af.sum(in_circle(x, y)) / samples + +def calc_pi_numpy(samples): + np.random.seed(1) + x = np.random.rand(samples).astype(np.float32) + y = np.random.rand(samples).astype(np.float32) + return 4. * np.sum(in_circle(x, y)) / samples + def calc_pi_host(samples): count = sum(1 for k in frange(samples) if in_circle(random(), random())) return 4 * float(count) / samples @@ -53,4 +62,6 @@ def bench(calc_pi, samples=1000000, iters=25): af.info() bench(calc_pi_device) + if np: + bench(calc_pi_numpy) bench(calc_pi_host) diff --git a/examples/common/idxio.py b/examples/common/idxio.py new file mode 100644 index 000000000..8776c62dc --- /dev/null +++ b/examples/common/idxio.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2019, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +def reverse_char(b): + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4 + b = (b & 0xCC) >> 2 | (b & 0x33) << 2 + b = (b & 0xAA) >> 1 | (b & 0x55) << 1 + return b + + +# http://stackoverflow.com/a/9144870/2192361 +def reverse(x): + x = ((x >> 1) & 0x55555555) | ((x & 0x55555555) << 1) + x = ((x >> 2) & 0x33333333) | ((x & 0x33333333) << 2) + x = ((x >> 4) & 0x0f0f0f0f) | ((x & 0x0f0f0f0f) << 4) + x = ((x >> 8) & 0x00ff00ff) | ((x & 0x00ff00ff) << 8) + x = ((x >> 16) & 0xffff) | ((x & 0xffff) << 16); + return x + + +def read_idx(name): + with open(name, 'rb') as f: + # In the C++ version, bytes the size of 4 chars are being read + # May not work properly in machines where a char is not 1 byte + bytes_read = f.read(4) + bytes_read = bytearray(bytes_read) + + if bytes_read[2] != 8: + raise RuntimeError('Unsupported data type') + + numdims = bytes_read[3] + elemsize = 1 + + # Read the dimensions + elem = 1 + dims = [0] * numdims + for i in range(numdims): + bytes_read = bytearray(f.read(4)) + + # Big endian to little endian + for j in range(4): + bytes_read[j] = reverse_char(bytes_read[j]) + bytes_read_int = int.from_bytes(bytes_read, 'little') + dim = reverse(bytes_read_int) + + elem = elem * dim; + dims[i] = dim; + + # Read the data + cdata = f.read(elem * elemsize) + cdata = list(cdata) + data = [float(cdata_elem) for cdata_elem in cdata] + + return (dims, data) + + diff --git a/examples/computer_vision/fast.py b/examples/computer_vision/fast.py new file mode 100644 index 000000000..e3a1299e3 --- /dev/null +++ b/examples/computer_vision/fast.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2018, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +from time import time +import arrayfire as af +import os +import sys + +def draw_corners(img, x, y, draw_len): + # Draw vertical line of (draw_len * 2 + 1) pixels centered on the corner + # Set only the first channel to 1 (green lines) + xmin = max(0, x - draw_len) + xmax = min(img.dims()[1], x + draw_len) + + img[y, xmin : xmax, 0] = 0.0 + img[y, xmin : xmax, 1] = 1.0 + img[y, xmin : xmax, 2] = 0.0 + + # Draw vertical line of (draw_len * 2 + 1) pixels centered on the corner + # Set only the first channel to 1 (green lines) + ymin = int(max(0, y - draw_len)) + ymax = int(min(img.dims()[0], y + draw_len)) + + img[ymin : ymax, x, 0] = 0.0 + img[ymin : ymax, x, 1] = 1.0 + img[ymin : ymax, x, 2] = 0.0 + return img + +def fast_demo(console): + + root_path = os.path.dirname(os.path.abspath(__file__)) + file_path = root_path + if console: + file_path += "/../../assets/examples/images/square.png" + else: + file_path += "/../../assets/examples/images/man.jpg" + img_color = af.load_image(file_path, True); + + img = af.color_space(img_color, af.CSPACE.GRAY, af.CSPACE.RGB) + img_color /= 255.0 + + features = af.fast(img) + + xs = features.get_xpos().to_list() + ys = features.get_ypos().to_list() + + draw_len = 3; + num_features = features.num_features().value + for f in range(num_features): + print(f) + x = xs[f] + y = ys[f] + + img_color = draw_corners(img_color, x, y, draw_len) + + + print("Features found: {}".format(num_features)) + if not console: + # Previews color image with green crosshairs + wnd = af.Window(512, 512, "FAST Feature Detector") + + while not wnd.close(): + wnd.image(img_color) + else: + print(xs); + print(ys); + + +if __name__ == "__main__": + if (len(sys.argv) > 1): + af.set_device(int(sys.argv[1])) + console = (sys.argv[2] == '-') if len(sys.argv) > 2 else False + + af.info() + print("** ArrayFire FAST Feature Detector Demo **\n") + fast_demo(console) + diff --git a/examples/computer_vision/harris.py b/examples/computer_vision/harris.py new file mode 100644 index 000000000..27fd6c6f8 --- /dev/null +++ b/examples/computer_vision/harris.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2018, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +from time import time +import arrayfire as af +import os +import sys + +def draw_corners(img, x, y, draw_len): + # Draw vertical line of (draw_len * 2 + 1) pixels centered on the corner + # Set only the first channel to 1 (green lines) + xmin = max(0, x - draw_len) + xmax = min(img.dims()[1], x + draw_len) + + img[y, xmin : xmax, 0] = 0.0 + img[y, xmin : xmax, 1] = 1.0 + img[y, xmin : xmax, 2] = 0.0 + + # Draw vertical line of (draw_len * 2 + 1) pixels centered on the corner + # Set only the first channel to 1 (green lines) + ymin = max(0, y - draw_len) + ymax = min(img.dims()[0], y + draw_len) + + img[ymin : ymax, x, 0] = 0.0 + img[ymin : ymax, x, 1] = 1.0 + img[ymin : ymax, x, 2] = 0.0 + return img + +def harris_demo(console): + + root_path = os.path.dirname(os.path.abspath(__file__)) + file_path = root_path + if console: + file_path += "/../../assets/examples/images/square.png" + else: + file_path += "/../../assets/examples/images/man.jpg" + img_color = af.load_image(file_path, True); + + img = af.color_space(img_color, af.CSPACE.GRAY, af.CSPACE.RGB) + img_color /= 255.0 + + ix, iy = af.gradient(img) + ixx = ix * ix + ixy = ix * iy + iyy = iy * iy + + # Compute a Gaussian kernel with standard deviation of 1.0 and length of 5 pixels + # These values can be changed to use a smaller or larger window + gauss_filt = af.gaussian_kernel(5, 5, 1.0, 1.0) + + # Filter second order derivatives + ixx = af.convolve(ixx, gauss_filt) + ixy = af.convolve(ixy, gauss_filt) + iyy = af.convolve(iyy, gauss_filt) + + # Calculate trace + itr = ixx + iyy + + # Calculate determinant + idet = ixx * iyy - ixy * ixy + + # Calculate Harris response + response = idet - 0.04 * (itr * itr) + + # Get maximum response for each 3x3 neighborhood + mask = af.constant(1, 3, 3) + max_resp = af.dilate(response, mask) + + # Discard responses that are not greater than threshold + corners = response > 1e5 + corners = corners * response + + # Discard responses that are not equal to maximum neighborhood response, + # scale them to original value + corners = (corners == max_resp) * corners + + # Copy device array to python list on host + corners_list = corners.to_list() + + draw_len = 3 + good_corners = 0 + for x in range(img_color.dims()[1]): + for y in range(img_color.dims()[0]): + if corners_list[x][y] > 1e5: + img_color = draw_corners(img_color, x, y, draw_len) + good_corners += 1 + + + print("Corners found: {}".format(good_corners)) + if not console: + # Previews color image with green crosshairs + wnd = af.Window(512, 512, "Harris Feature Detector") + + while not wnd.close(): + wnd.image(img_color) + else: + idx = af.where(corners) + + corners_x = idx / float(corners.dims()[0]) + corners_y = idx % float(corners.dims()[0]) + + print(corners_x) + print(corners_y) + + +if __name__ == "__main__": + if (len(sys.argv) > 1): + af.set_device(int(sys.argv[1])) + console = (sys.argv[2] == '-') if len(sys.argv) > 2 else False + + af.info() + print("** ArrayFire Harris Corner Detector Demo **\n") + + harris_demo(console) + diff --git a/examples/computer_vision/matching.py b/examples/computer_vision/matching.py new file mode 100644 index 000000000..cab0cccdf --- /dev/null +++ b/examples/computer_vision/matching.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2018, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +from time import time +import arrayfire as af +import os +import sys + +def normalize(a): + max_ = float(af.max(a)) + min_ = float(af.min(a)) + return (a - min_) / (max_ - min_) + +def draw_rectangle(img, x, y, wx, wy): + print("\nMatching patch origin = ({}, {})\n".format(x, y)) + + # top edge + img[y, x : x + wx, 0] = 0.0 + img[y, x : x + wx, 1] = 0.0 + img[y, x : x + wx, 2] = 1.0 + + # bottom edge + img[y + wy, x : x + wx, 0] = 0.0 + img[y + wy, x : x + wx, 1] = 0.0 + img[y + wy, x : x + wx, 2] = 1.0 + + # left edge + img[y : y + wy, x, 0] = 0.0 + img[y : y + wy, x, 1] = 0.0 + img[y : y + wy, x, 2] = 1.0 + + # left edge + img[y : y + wy, x + wx, 0] = 0.0 + img[y : y + wy, x + wx, 1] = 0.0 + img[y : y + wy, x + wx, 2] = 1.0 + + return img + +def templateMatchingDemo(console): + + root_path = os.path.dirname(os.path.abspath(__file__)) + file_path = root_path + if console: + file_path += "/../../assets/examples/images/square.png" + else: + file_path += "/../../assets/examples/images/man.jpg" + img_color = af.load_image(file_path, True); + + # Convert the image from RGB to gray-scale + img = af.color_space(img_color, af.CSPACE.GRAY, af.CSPACE.RGB) + iDims = img.dims() + print("Input image dimensions: ", iDims) + + # Extract a patch from the input image + patch_size = 100 + tmp_img = img[100 : 100+patch_size, 100 : 100+patch_size] + + result = af.match_template(img, tmp_img) # Default disparity metric is + # Sum of Absolute differences (SAD) + # Currently supported metrics are + # AF_SAD, AF_ZSAD, AF_LSAD, AF_SSD, + # AF_ZSSD, AF_LSSD + + disp_img = img / 255.0 + disp_tmp = tmp_img / 255.0 + disp_res = normalize(result) + + minval, minloc = af.imin(disp_res) + print("Location(linear index) of minimum disparity value = {}".format(minloc)) + + if not console: + marked_res = af.tile(disp_img, 1, 1, 3) + marked_res = draw_rectangle(marked_res, minloc%iDims[0], minloc/iDims[0],\ + patch_size, patch_size) + + print("Note: Based on the disparity metric option provided to matchTemplate function") + print("either minimum or maximum disparity location is the starting corner") + print("of our best matching patch to template image in the search image") + + wnd = af.Window(512, 512, "Template Matching Demo") + + while not wnd.close(): + wnd.set_colormap(af.COLORMAP.DEFAULT) + wnd.grid(2, 2) + wnd[0, 0].image(disp_img, "Search Image" ) + wnd[0, 1].image(disp_tmp, "Template Patch" ) + wnd[1, 0].image(marked_res, "Best Match" ) + wnd.set_colormap(af.COLORMAP.HEAT) + wnd[1, 1].image(disp_res, "Disparity Values") + wnd.show() + + +if __name__ == "__main__": + if (len(sys.argv) > 1): + af.set_device(int(sys.argv[1])) + console = (sys.argv[2] == '-') if len(sys.argv) > 2 else False + + af.info() + print("** ArrayFire template matching Demo **\n") + templateMatchingDemo(console) + diff --git a/examples/computer_vision/susan.py b/examples/computer_vision/susan.py new file mode 100644 index 000000000..37a5dbd11 --- /dev/null +++ b/examples/computer_vision/susan.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2018, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +from time import time +import arrayfire as af +import os +import sys + +def draw_corners(img, x, y, draw_len): + # Draw vertical line of (draw_len * 2 + 1) pixels centered on the corner + # Set only the first channel to 1 (green lines) + xmin = max(0, x - draw_len) + xmax = min(img.dims()[1], x + draw_len) + + img[y, xmin : xmax, 0] = 0.0 + img[y, xmin : xmax, 1] = 1.0 + img[y, xmin : xmax, 2] = 0.0 + + # Draw vertical line of (draw_len * 2 + 1) pixels centered on the corner + # Set only the first channel to 1 (green lines) + ymin = max(0, y - draw_len) + ymax = min(img.dims()[0], y + draw_len) + + img[ymin : ymax, x, 0] = 0.0 + img[ymin : ymax, x, 1] = 1.0 + img[ymin : ymax, x, 2] = 0.0 + return img + +def susan_demo(console): + + root_path = os.path.dirname(os.path.abspath(__file__)) + file_path = root_path + if console: + file_path += "/../../assets/examples/images/square.png" + else: + file_path += "/../../assets/examples/images/man.jpg" + img_color = af.load_image(file_path, True); + + img = af.color_space(img_color, af.CSPACE.GRAY, af.CSPACE.RGB) + img_color /= 255.0 + + features = af.susan(img) + + xs = features.get_xpos().to_list() + ys = features.get_ypos().to_list() + + draw_len = 3; + num_features = features.num_features().value + for f in range(num_features): + print(f) + x = xs[f] + y = ys[f] + + # TODO fix coord order to x,y after upstream fix + img_color = draw_corners(img_color, y, x, draw_len) + + + print("Features found: {}".format(num_features)) + if not console: + # Previews color image with green crosshairs + wnd = af.Window(512, 512, "SUSAN Feature Detector") + + while not wnd.close(): + wnd.image(img_color) + else: + print(xs); + print(ys); + + +if __name__ == "__main__": + if (len(sys.argv) > 1): + af.set_device(int(sys.argv[1])) + console = (sys.argv[2] == '-') if len(sys.argv) > 2 else False + + af.info() + print("** ArrayFire SUSAN Feature Detector Demo **\n") + susan_demo(console) + diff --git a/examples/financial/heston_model.py b/examples/financial/heston_model.py index 041f9d3c2..640127eeb 100644 --- a/examples/financial/heston_model.py +++ b/examples/financial/heston_model.py @@ -67,9 +67,9 @@ def simulateHestonModel( T, N, R, mu, kappa, vBar, sigmaV, rho, x0, v0 ) : def main(): T = 1 - nT = 10 * T + nT = 20 * T R_first = 1000 - R = 20000000 + R = 5000000 x0 = 0 # initial log stock price v0 = 0.087**2 # initial volatility diff --git a/examples/graphics/conway.py b/examples/graphics/conway.py index f4d515c0a..49ad43bb9 100644 --- a/examples/graphics/conway.py +++ b/examples/graphics/conway.py @@ -66,7 +66,7 @@ A2 = (state == 0) & C1 A3 = (state == 1) & (neighborhood > 3) - display = (af.join(2, A0 + A1, A1 + A2, A3).as_type(af.Dtype.f32) + display = af.join(2, A0 + A1, A1 + A2, A3).as_type(af.Dtype.f32) state = state * C0 + C1 diff --git a/examples/graphics/histogram.py b/examples/graphics/histogram.py index 3a3d29ae5..9a15dcf30 100644 --- a/examples/graphics/histogram.py +++ b/examples/graphics/histogram.py @@ -29,7 +29,7 @@ hist_win = af.Window(512, 512, "3D Plot example using ArrayFire") img_win = af.Window(480, 640, "Input Image") - img = (af.load_image(sys.argv[1])).(af.Dtype.u8) + img = af.load_image(sys.argv[1]).as_type(af.Dtype.u8) hist = af.histogram(img, 256, 0, 255) while (not hist_win.close()) and (not img_win.close()): diff --git a/examples/helloworld/helloworld.py b/examples/helloworld/helloworld.py index 37ef89500..2d76cf3ea 100755 --- a/examples/helloworld/helloworld.py +++ b/examples/helloworld/helloworld.py @@ -11,14 +11,51 @@ import arrayfire as af -# Display backend information -af.info() +try: + # Display backend information + af.info() -# Generate a uniform random array with a size of 5 elements -a = af.randu(5, 1) + print("Create a 5-by-3 matrix of random floats on the GPU\n") + A = af.randu(5, 3, 1, 1, af.Dtype.f32) + af.display(A) -# Print a and its minimum value -print(a) + print("Element-wise arithmetic\n") + B = af.sin(A) + 1.5 + af.display(B) -# Print min and max values of a -print("Minimum, Maximum: ", af.min(a), af.max(a)) + print("Negate the first three elements of second column\n") + B[0:3, 1] = B[0:3, 1] * -1 + af.display(B) + + print("Fourier transform the result\n"); + C = af.fft(B); + af.display(C); + + print("Grab last row\n"); + c = C[-1,:]; + af.display(c); + + print("Scan Test\n"); + r = af.constant(2, 16, 4, 1, 1); + af.display(r); + + print("Scan\n"); + S = af.scan(r, 0, af.BINARYOP.MUL); + af.display(S); + + print("Create 2-by-3 matrix from host data\n"); + d = [ 1, 2, 3, 4, 5, 6 ] + D = af.Array(d, (2, 3)) + af.display(D) + + print("Copy last column onto first\n"); + D[:,0] = D[:, -1] + af.display(D); + + print("Sort A and print sorted array and corresponding indices\n"); + [sorted_vals, sorted_idxs] = af.sort_index(A); + af.display(A) + af.display(sorted_vals) + af.display(sorted_idxs) +except Exception as e: + print("Error: " + str(e)) diff --git a/examples/lin_algebra/cholesky.py b/examples/lin_algebra/cholesky.py new file mode 100644 index 000000000..b3e550139 --- /dev/null +++ b/examples/lin_algebra/cholesky.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +####################################################### +# Copyright (c) 2022, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +import arrayfire as af + +def main(): + try: + af.info() + + n = 5 + t = af.randu(n, n) + arr_in = af.matmulNT(t, t) + af.identity(n, n) * n + + print("Running Cholesky InPlace\n") + cin_upper = arr_in.copy() + cin_lower = arr_in.copy() + + af.cholesky_inplace(cin_upper, True) + af.cholesky_inplace(cin_lower, False) + + print(cin_upper) + print(cin_lower) + + print("\nRunning Cholesky Out of place\n") + + out_upper, upper_success = af.cholesky(arr_in, True) + out_lower, lower_success = af.cholesky(arr_in, False) + + if upper_success == 0: + print(out_upper) + if lower_success == 0: + print(out_lower) + + except Exception as e: + print('Error: ', str(e)) + +if __name__ == '__main__': + main() diff --git a/examples/lin_algebra/lu.py b/examples/lin_algebra/lu.py new file mode 100644 index 000000000..14405cc29 --- /dev/null +++ b/examples/lin_algebra/lu.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +####################################################### +# Copyright (c) 2022, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +import arrayfire as af + +def main(): + try: + af.info() + + in_array = af.randu(5,8) + + print("Running LU InPlace\n") + pivot = af.lu_inplace(in_array) + print(in_array) + print(pivot) + + print("Running LU with Upper Lower Factorization\n") + lower, upper, pivot = af.lu(in_array) + print(lower) + print(upper) + print(pivot) + + except Exception as e: + print('Error: ', str(e)) + +if __name__ == '__main__': + main() + diff --git a/examples/lin_algebra/qr.py b/examples/lin_algebra/qr.py new file mode 100644 index 000000000..adeebfd0e --- /dev/null +++ b/examples/lin_algebra/qr.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +####################################################### +# Copyright (c) 2022, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +import arrayfire as af + +def main(): + try: + af.info() + in_array = af.randu(5,8) + + print("Running QR InPlace\n") + q_in = in_array.copy() + print(q_in) + + tau = af.qr_inplace(q_in) + + print(q_in) + print(tau) + + print("Running QR with Q and R factorization\n") + q, r, tau = af.qr(in_array) + + print(q) + print(r) + print(tau) + + except Exception as e: + print("Error: ", str(e)) + +if __name__ == '__main__': + main() diff --git a/examples/machine_learning/logistic_regression.py b/examples/machine_learning/logistic_regression.py new file mode 100644 index 000000000..b1d8b6e42 --- /dev/null +++ b/examples/machine_learning/logistic_regression.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2019, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +from mnist_common import display_results, setup_mnist + +import sys +import time + +import arrayfire as af +from arrayfire.algorithm import max, imax, count, sum +from arrayfire.arith import abs, sigmoid, log +from arrayfire.array import transpose +from arrayfire.blas import matmul, matmulTN +from arrayfire.data import constant, join, lookup, moddims +from arrayfire.device import set_device, sync, eval + + +def accuracy(predicted, target): + _, tlabels = af.imax(target, 1) + _, plabels = af.imax(predicted, 1) + return 100 * af.count(plabels == tlabels) / tlabels.elements() + + +def abserr(predicted, target): + return 100 * af.sum(af.abs(predicted - target)) / predicted.elements() + + +# Predict (probability) based on given parameters +def predict_prob(X, Weights): + Z = af.matmul(X, Weights) + return af.sigmoid(Z) + + +# Predict (log probability) based on given parameters +def predict_log_prob(X, Weights): + return af.log(predict_prob(X, Weights)) + + +# Give most likely class based on given parameters +def predict_class(X, Weights): + probs = predict_prob(X, Weights) + _, classes = af.imax(probs, 1) + return classes + + +def cost(Weights, X, Y, lambda_param=1.0): + # Number of samples + m = Y.dims()[0] + + dim0 = Weights.dims()[0] + dim1 = Weights.dims()[1] if len(Weights.dims()) > 1 else None + dim2 = Weights.dims()[2] if len(Weights.dims()) > 2 else None + dim3 = Weights.dims()[3] if len(Weights.dims()) > 3 else None + # Make the lambda corresponding to Weights(0) == 0 + lambdat = af.constant(lambda_param, dim0, dim1, dim2, dim3) + + # No regularization for bias weights + lambdat[0, :] = 0 + + # Get the prediction + H = predict_prob(X, Weights) + + # Cost of misprediction + Jerr = -1 * af.sum(Y * af.log(H) + (1 - Y) * af.log(1 - H), dim=0) + + # Regularization cost + Jreg = 0.5 * af.sum(lambdat * Weights * Weights, dim=0) + + # Total cost + J = (Jerr + Jreg) / m + + # Find the gradient of cost + D = (H - Y) + dJ = (af.matmulTN(X, D) + lambdat * Weights) / m + + return J, dJ + + +def train(X, Y, alpha=0.1, lambda_param=1.0, maxerr=0.01, maxiter=1000, verbose=False): + # Initialize parameters to 0 + Weights = af.constant(0, X.dims()[1], Y.dims()[1]) + + for i in range(maxiter): + # Get the cost and gradient + J, dJ = cost(Weights, X, Y, lambda_param) + + err = af.max(af.abs(J)) + if err < maxerr: + print('Iteration {0:4d} Err: {1:4f}'.format(i + 1, err)) + print('Training converged') + return Weights + + if verbose and ((i+1) % 10 == 0): + print('Iteration {0:4d} Err: {1:4f}'.format(i + 1, err)) + + # Update the parameters via gradient descent + Weights = Weights - alpha * dJ + + if verbose: + print('Training stopped after {0:d} iterations'.format(maxiter)) + + return Weights + + +def benchmark_logistic_regression(train_feats, train_targets, test_feats): + t0 = time.time() + Weights = train(train_feats, train_targets, 0.1, 1.0, 0.01, 1000) + af.eval(Weights) + sync() + t1 = time.time() + dt = t1 - t0 + print('Training time: {0:4.4f} s'.format(dt)) + + t0 = time.time() + iters = 100 + for i in range(iters): + test_outputs = predict_prob(test_feats, Weights) + af.eval(test_outputs) + sync() + t1 = time.time() + dt = t1 - t0 + print('Prediction time: {0:4.4f} s'.format(dt / iters)) + + +# Demo of one vs all logistic regression +def logit_demo(console, perc): + # Load mnist data + frac = float(perc) / 100.0 + mnist_data = setup_mnist(frac, True) + num_classes = mnist_data[0] + num_train = mnist_data[1] + num_test = mnist_data[2] + train_images = mnist_data[3] + test_images = mnist_data[4] + train_targets = mnist_data[5] + test_targets = mnist_data[6] + + # Reshape images into feature vectors + feature_length = int(train_images.elements() / num_train); + train_feats = af.transpose(af.moddims(train_images, feature_length, num_train)) + test_feats = af.transpose(af.moddims(test_images, feature_length, num_test)) + + train_targets = af.transpose(train_targets) + test_targets = af.transpose(test_targets) + + num_train = train_feats.dims()[0] + num_test = test_feats.dims()[0] + + # Add a bias that is always 1 + train_bias = af.constant(1, num_train, 1) + test_bias = af.constant(1, num_test, 1) + train_feats = af.join(1, train_bias, train_feats) + test_feats = af.join(1, test_bias, test_feats) + + # Train logistic regression parameters + Weights = train(train_feats, train_targets, + 0.1, # learning rate + 1.0, # regularization constant + 0.01, # max error + 1000, # max iters + True # verbose mode + ) + af.eval(Weights) + af.sync() + + # Predict the results + train_outputs = predict_prob(train_feats, Weights) + test_outputs = predict_prob(test_feats, Weights) + + print('Accuracy on training data: {0:2.2f}'.format(accuracy(train_outputs, train_targets))) + print('Accuracy on testing data: {0:2.2f}'.format(accuracy(test_outputs, test_targets))) + print('Maximum error on testing data: {0:2.2f}'.format(abserr(test_outputs, test_targets))) + + benchmark_logistic_regression(train_feats, train_targets, test_feats) + + if not console: + test_outputs = af.transpose(test_outputs) + # Get 20 random test images + display_results(test_images, test_outputs, af.transpose(test_targets), 20, True) + +def main(): + argc = len(sys.argv) + + device = int(sys.argv[1]) if argc > 1 else 0 + console = sys.argv[2][0] == '-' if argc > 2 else False + perc = int(sys.argv[3]) if argc > 3 else 60 + + try: + af.set_device(device) + af.info() + logit_demo(console, perc) + except Exception as e: + print('Error: ', str(e)) + + +if __name__ == '__main__': + main() diff --git a/examples/machine_learning/mnist_common.py b/examples/machine_learning/mnist_common.py new file mode 100644 index 000000000..3f38302df --- /dev/null +++ b/examples/machine_learning/mnist_common.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2019, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +import os +import sys +sys.path.insert(0, '../common') +from idxio import read_idx + +import arrayfire as af +from arrayfire.algorithm import where +from arrayfire.array import Array +from arrayfire.data import constant, lookup, moddims +from arrayfire.random import randu + + +def classify(arr, k, expand_labels): + ret_str = '' + if expand_labels: + vec = arr[:, k].as_type(af.Dtype.f32) + h_vec = vec.to_list() + data = [] + + for i in range(vec.elements()): + data.append((h_vec[i], i)) + + data = sorted(data, key=lambda pair: pair[0], reverse=True) + + ret_str = str(data[0][1]) + + else: + ret_str = str(int(arr[k].as_type(af.Dtype.f32).scalar())) + + return ret_str + + +def setup_mnist(frac, expand_labels): + root_path = os.path.dirname(os.path.abspath(__file__)) + file_path = root_path + '/../../assets/examples/data/mnist/' + idims, idata = read_idx(file_path + 'images-subset') + ldims, ldata = read_idx(file_path + 'labels-subset') + + idims.reverse() + numdims = len(idims) + images = af.Array(idata, tuple(idims)) + + R = af.randu(10000, 1); + cond = R < min(frac, 0.8) + train_indices = af.where(cond) + test_indices = af.where(~cond) + + train_images = af.lookup(images, train_indices, 2) / 255 + test_images = af.lookup(images, test_indices, 2) / 255 + + num_classes = 10 + num_train = train_images.dims()[2] + num_test = test_images.dims()[2] + + if expand_labels: + train_labels = af.constant(0, num_classes, num_train) + test_labels = af.constant(0, num_classes, num_test) + + h_train_idx = train_indices.to_list() + h_test_idx = test_indices.to_list() + + for i in range(num_train): + train_labels[ldata[h_train_idx[i]], i] = 1 + + for i in range(num_test): + test_labels[ldata[h_test_idx[i]], i] = 1 + + else: + labels = af.Array(ldata, tuple(ldims)) + train_labels = labels[train_indices] + test_labels = labels[test_indices] + + return (num_classes, + num_train, + num_test, + train_images, + test_images, + train_labels, + test_labels) + + +def display_results(test_images, test_output, test_actual, num_display, expand_labels): + for i in range(num_display): + print('Predicted: ', classify(test_output, i, expand_labels)) + print('Actual: ', classify(test_actual, i, expand_labels)) + + img = (test_images[:, :, i] > 0.1).as_type(af.Dtype.u8) + img = af.moddims(img, img.elements()).to_list() + for j in range(28): + for k in range(28): + print('\u2588' if img[j * 28 + k] > 0 else ' ', end='') + print() + input() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..75335283e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[build-system] +requires = ["setuptools", "wheel", "scikit-build", "cmake", "ninja"] 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 new file mode 100644 index 000000000..b40ac55e5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,63 @@ +[metadata] +name = arrayfire +version = 3.8.0 +description = Python bindings for ArrayFire +licence = BSD +long_description = file: README.md +long_description_content_type = text/markdown +maintainer = ArrayFire +maintainer_email = technical@arrayfire.com +url = http://arrayfire.com +classifiers = + Programming Language :: Python + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + +[options] +packages = find: +install_requires = + scikit-build +python_requires = + >=3.8.0 + +[options.packages.find] +include = arrayfire +exclude = + examples + tests +install_requires = + numpy~=1.22.0 + +[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 diff --git a/setup.py b/setup.py index 2974351b9..b6d37bb86 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2015, ArrayFire @@ -9,19 +9,98 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## -from setuptools import setup, find_packages - -## TODO: -## 1) Look for af libraries during setup -## 2) Include test suite - -setup( - author="Pavan Yalamanchili", - author_email="pavan@arrayfire.com", - name="arrayfire", - version="3.3.0", - description="Python bindings for ArrayFire", - license="BSD", - url="http://arrayfire.com", - packages=find_packages(exclude=['examples', 'tests']), -) +import os +import re + +# package can be distributed with arrayfire binaries or +# just with python wrapper files, the AF_BUILD_LOCAL +# environment var determines whether to build the arrayfire +# binaries locally rather than searching in a system install + +AF_BUILD_LOCAL_LIBS = os.environ.get('AF_BUILD_LOCAL_LIBS') +print(f'AF_BUILD_LOCAL_LIBS={AF_BUILD_LOCAL_LIBS}') +if AF_BUILD_LOCAL_LIBS: + print('Proceeding to build ArrayFire libraries') +else: + print('Skipping binaries installation, only python files will be installed') + +AF_BUILD_CPU = os.environ.get('AF_BUILD_CPU') +AF_BUILD_CPU = 1 if AF_BUILD_CPU is None else int(AF_BUILD_CPU) +AF_BUILD_CPU_CMAKE_STR = '-DAF_BUILD_CPU:BOOL=ON' if (AF_BUILD_CPU == 1) else '-DAF_BUILD_CPU:BOOL=OFF' + +AF_BUILD_CUDA = os.environ.get('AF_BUILD_CUDA') +AF_BUILD_CUDA = 1 if AF_BUILD_CUDA is None else int(AF_BUILD_CUDA) +AF_BUILD_CUDA_CMAKE_STR = '-DAF_BUILD_CUDA:BOOL=ON' if (AF_BUILD_CUDA == 1) else '-DAF_BUILD_CUDA:BOOL=OFF' + +AF_BUILD_OPENCL = os.environ.get('AF_BUILD_OPENCL') +AF_BUILD_OPENCL = 1 if AF_BUILD_OPENCL is None else int(AF_BUILD_OPENCL) +AF_BUILD_OPENCL_CMAKE_STR = '-DAF_BUILD_OPENCL:BOOL=ON' if (AF_BUILD_OPENCL == 1) else '-DAF_BUILD_OPENCL:BOOL=OFF' + +AF_BUILD_UNIFIED = os.environ.get('AF_BUILD_UNIFIED') +AF_BUILD_UNIFIED = 1 if AF_BUILD_UNIFIED is None else int(AF_BUILD_UNIFIED) +AF_BUILD_UNIFIED_CMAKE_STR = '-DAF_BUILD_UNIFIED:BOOL=ON' if (AF_BUILD_UNIFIED == 1) else '-DAF_BUILD_UNIFIED:BOOL=OFF' + +if AF_BUILD_LOCAL_LIBS: + # invoke cmake and build arrayfire libraries to install locally in package + from skbuild import setup + + def filter_af_files(cmake_manifest): + cmake_manifest = list(filter(lambda name: not (name.endswith('.h') + or name.endswith('.cpp') + or name.endswith('.hpp') + or name.endswith('.cmake') + or name.endswith('jpg') + or name.endswith('png') + or name.endswith('libaf.so') #avoids duplicates due to symlinks + or re.match('.*libaf\.so\.3\..*', name) is not None + or name.endswith('libafcpu.so') + or re.match('.*libafcpu\.so\.3\..*', name) is not None + or name.endswith('libafcuda.so') + or re.match('.*libafcuda\.so\.3\..*', name) is not None + or name.endswith('libafopencl.so') + or re.match('.*libafopencl\.so\.3\..*', name) is not None + or name.endswith('libforge.so') + or re.match('.*libforge\.so\.1\..*', name) is not None + or 'examples' in name), cmake_manifest)) + return cmake_manifest + + print('Building CMAKE with following configurable variables: ') + print(AF_BUILD_CPU_CMAKE_STR) + print(AF_BUILD_CUDA_CMAKE_STR) + print(AF_BUILD_OPENCL_CMAKE_STR) + print(AF_BUILD_UNIFIED_CMAKE_STR) + + + setup( + packages=['arrayfire'], + cmake_install_dir='', + cmake_process_manifest_hook=filter_af_files, + include_package_data=False, + cmake_args=[AF_BUILD_CPU_CMAKE_STR, + AF_BUILD_CUDA_CMAKE_STR, + AF_BUILD_OPENCL_CMAKE_STR, + AF_BUILD_UNIFIED_CMAKE_STR, + # todo: pass additional args from environ + '-DCMAKE_BUILD_TYPE:STRING="RelWithDebInfo"', + '-DFG_USE_STATIC_CPPFLAGS:BOOL=OFF', + '-DFG_WITH_FREEIMAGE:BOOL=OFF', + '-DCUDA_architecture_build_targets:STRING=All', + '-DAF_BUILD_DOCS:BOOL=OFF', + '-DAF_BUILD_EXAMPLES:BOOL=OFF', + '-DAF_INSTALL_STANDALONE:BOOL=ON', + '-DAF_WITH_IMAGEIO:BOOL=ON', + '-DAF_WITH_LOGGING:BOOL=ON', + '-DBUILD_TESTING:BOOL=OFF', + '-DAF_BUILD_FORGE:BOOL=ON', + '-DAF_INSTALL_LIB_DIR:STRING=arrayfire', + '-DAF_INSTALL_BIN_DIR:STRING=arrayfire', + '-DFG_INSTALL_LIB_DIR:STRING=arrayfire', + '-DAF_WITH_STATIC_MKL=ON', + ] + ) + +else: + # ignores local arrayfire libraries, will search system instead + from setuptools import setup + setup() + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..24e9ac759 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2015, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## diff --git a/tests/__main__.py b/tests/__main__.py index 5b1e4738c..6bed94278 100644 --- a/tests/__main__.py +++ b/tests/__main__.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -7,36 +9,41 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## +from __future__ import absolute_import + import sys -from simple_tests import * + +from . import simple tests = {} tests['simple'] = simple.tests + def assert_valid(name, name_list, name_str): is_valid = any([name == val for val in name_list]) - if not is_valid: - err_str = "The first argument needs to be a %s name\n" % name_str - err_str += "List of supported %ss: %s" % (name_str, str(list(name_list))) - raise RuntimeError(err_str) + if is_valid: + return + err_str = "The first argument needs to be a %s name\n" % name_str + err_str += "List of supported %ss: %s" % (name_str, str(list(name_list))) + raise RuntimeError(err_str) -if __name__ == "__main__": +if __name__ == "__main__": module_name = None num_args = len(sys.argv) - if (num_args > 1): + if num_args > 1: module_name = sys.argv[1].lower() assert_valid(sys.argv[1].lower(), tests.keys(), "module") - if (module_name is None): + if module_name is None: for name in tests: tests[name].run() else: test = tests[module_name] test_list = None - if (num_args > 2): + if num_args > 2: test_list = sys.argv[2:] for test_name in test_list: assert_valid(test_name.lower(), test.keys(), "test") diff --git a/tests/simple/__init__.py b/tests/simple/__init__.py index 465197202..4950136e3 100644 --- a/tests/simple/__init__.py +++ b/tests/simple/__init__.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -7,15 +9,36 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## -from .algorithm import * -from .arith import * -from .array_test import * -from .blas import * -from .data import * -from .device import * -from .image import * -from .index import * -from .lapack import * -from .signal import * -from .statistics import * from ._util import tests +from .algorithm import simple_algorithm +from .arith import simple_arith +from .array_test import simple_array +from .blas import simple_blas +from .data import simple_data +from .device import simple_device +from .image import simple_image +from .index import simple_index +from .interop import simple_interop +from .lapack import simple_lapack +from .random import simple_random +from .signal import simple_signal +from .sparse import simple_sparse +from .statistics import simple_statistics + +__all__ = [ + "tests", + "simple_algorithm", + "simple_arith", + "simple_array", + "simple_blas", + "simple_data", + "simple_device", + "simple_image", + "simple_index", + "simple_interop", + "simple_lapack", + "simple_random", + "simple_signal", + "simple_sparse", + "simple_statistics" +] diff --git a/tests/simple/_util.py b/tests/simple/_util.py index 7af14367b..2275cf2fc 100644 --- a/tests/simple/_util.py +++ b/tests/simple/_util.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -7,48 +9,54 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## -import traceback import logging -import arrayfire as af +import sys +import traceback -def display_func(verbose): - if (verbose): - return af.display - else: - def eval_func(foo): - res = foo - return eval_func - -def print_func(verbose): - def print_func_impl(*args): - if (verbose): - print(args) - else: - res = [args] - return print_func_impl class _simple_test_dict(dict): - def __init__(self): self.print_str = "Simple %16s: %s" + self.failed = False super(_simple_test_dict, self).__init__() def run(self, name_list=None, verbose=False): test_list = name_list if name_list is not None else self.keys() for key in test_list: + self.print_log = "" try: test = self[key] - except: + except KeyError: print(self.print_str % (key, "NOTFOUND")) continue try: test(verbose) print(self.print_str % (key, "PASSED")) - except Exception as e: + except Exception: print(self.print_str % (key, "FAILED")) - if (verbose): - logging.error(traceback.format_exc()) + self.failed = True + if not verbose: + print(tests.print_log) + logging.error(traceback.format_exc()) + + if self.failed: + sys.exit(1) tests = _simple_test_dict() + + +def print_func(verbose): + def print_func_impl(*args): + _print_log = "" + for arg in args: + _print_log += str(arg) + '\n' + if verbose: + print(_print_log) + tests.print_log += _print_log + return print_func_impl + + +def display_func(verbose): + return print_func(verbose) diff --git a/tests/simple/algorithm.py b/tests/simple/algorithm.py index f68e354e4..b9e42f138 100644 --- a/tests/simple/algorithm.py +++ b/tests/simple/algorithm.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -9,20 +10,47 @@ ######################################################## import arrayfire as af + from . import _util -def simple_algorithm(verbose = False): + +def simple_algorithm(verbose=False): display_func = _util.display_func(verbose) - print_func = _util.print_func(verbose) + print_func = _util.print_func(verbose) a = af.randu(3, 3) + k = af.constant(1, 3, 3, dtype=af.Dtype.u32) + af.eval(k) - print_func(af.sum(a), af.product(a), af.min(a), af.max(a), - af.count(a), af.any_true(a), af.all_true(a)) + print_func(af.sum(a), af.product(a), af.min(a), af.max(a), af.count(a), af.any_true(a), af.all_true(a)) display_func(af.sum(a, 0)) display_func(af.sum(a, 1)) + rk = af.constant(1, 3, dtype=af.Dtype.u32) + rk[2] = 0 + af.eval(rk) + display_func(af.sumByKey(rk, a, dim=0)) + display_func(af.sumByKey(rk, a, dim=1)) + + display_func(af.productByKey(rk, a, dim=0)) + display_func(af.productByKey(rk, a, dim=1)) + + display_func(af.minByKey(rk, a, dim=0)) + display_func(af.minByKey(rk, a, dim=1)) + + display_func(af.maxByKey(rk, a, dim=0)) + display_func(af.maxByKey(rk, a, dim=1)) + + display_func(af.anyTrueByKey(rk, a, dim=0)) + display_func(af.anyTrueByKey(rk, a, dim=1)) + + display_func(af.allTrueByKey(rk, a, dim=0)) + display_func(af.allTrueByKey(rk, a, dim=1)) + + display_func(af.countByKey(rk, a, dim=0)) + display_func(af.countByKey(rk, a, dim=1)) + display_func(af.product(a, 0)) display_func(af.product(a, 1)) @@ -44,33 +72,39 @@ def simple_algorithm(verbose = False): display_func(af.accum(a, 0)) display_func(af.accum(a, 1)) + display_func(af.scan(a, 0, af.BINARYOP.ADD)) + display_func(af.scan(a, 1, af.BINARYOP.MAX)) + + display_func(af.scan_by_key(k, a, 0, af.BINARYOP.ADD)) + display_func(af.scan_by_key(k, a, 1, af.BINARYOP.MAX)) + display_func(af.sort(a, is_ascending=True)) display_func(af.sort(a, is_ascending=False)) b = (a > 0.1) * a c = (a > 0.4) * a d = b / c - print_func(af.sum(d)); - print_func(af.sum(d, nan_val=0.0)); - display_func(af.sum(d, dim=0, nan_val=0.0)); + print_func(af.sum(d)) + print_func(af.sum(d, nan_val=0.0)) + display_func(af.sum(d, dim=0, nan_val=0.0)) - val,idx = af.sort_index(a, is_ascending=True) + val, idx = af.sort_index(a, is_ascending=True) display_func(val) display_func(idx) - val,idx = af.sort_index(a, is_ascending=False) + val, idx = af.sort_index(a, is_ascending=False) display_func(val) display_func(idx) - b = af.randu(3,3) - keys,vals = af.sort_by_key(a, b, is_ascending=True) + b = af.randu(3, 3) + keys, vals = af.sort_by_key(a, b, is_ascending=True) display_func(keys) display_func(vals) - keys,vals = af.sort_by_key(a, b, is_ascending=False) + keys, vals = af.sort_by_key(a, b, is_ascending=False) display_func(keys) display_func(vals) - c = af.randu(5,1) - d = af.randu(5,1) + c = af.randu(5, 1) + d = af.randu(5, 1) cc = af.set_unique(c, is_sorted=False) dd = af.set_unique(af.sort(d), is_sorted=True) display_func(cc) @@ -82,4 +116,5 @@ def simple_algorithm(verbose = False): display_func(af.set_intersect(cc, cc, is_unique=True)) display_func(af.set_intersect(cc, cc, is_unique=False)) -_util.tests['algorithm'] = simple_algorithm + +_util.tests["algorithm"] = simple_algorithm diff --git a/tests/simple/arith.py b/tests/simple/arith.py index e55881e6c..5d4d83d00 100644 --- a/tests/simple/arith.py +++ b/tests/simple/arith.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -9,14 +10,15 @@ ######################################################## import arrayfire as af + from . import _util -def simple_arith(verbose = False): + +def simple_arith(verbose=False): display_func = _util.display_func(verbose) - print_func = _util.print_func(verbose) - a = af.randu(3,3,dtype=af.Dtype.u32) - b = af.constant(4, 3, 3, dtype=af.Dtype.u32) + a = af.randu(3, 3) + b = af.constant(4, 3, 3) display_func(a) display_func(b) @@ -29,7 +31,6 @@ def simple_arith(verbose = False): display_func(a + 2) display_func(3 + a) - c = a - b d = a d -= b @@ -99,6 +100,9 @@ def simple_arith(verbose = False): display_func(a == 0.5) display_func(0.5 == a) + a = af.randu(3, 3, dtype=af.Dtype.u32) + b = af.constant(4, 3, 3, dtype=af.Dtype.u32) + display_func(a & b) display_func(a & 2) c = a @@ -129,12 +133,17 @@ def simple_arith(verbose = False): display_func(a) display_func(af.cast(a, af.Dtype.c32)) - display_func(af.maxof(a,b)) - display_func(af.minof(a,b)) - display_func(af.rem(a,b)) + display_func(af.maxof(a, b)) + display_func(af.minof(a, b)) + + display_func(af.clamp(a, 0, 1)) + display_func(af.clamp(a, 0, b)) + display_func(af.clamp(a, b, 1)) - a = af.randu(3,3) - 0.5 - b = af.randu(3,3) - 0.5 + display_func(af.rem(a, b)) + + a = af.randu(3, 3) - 0.5 + b = af.randu(3, 3) - 0.5 display_func(af.abs(a)) display_func(af.arg(a)) @@ -153,7 +162,7 @@ def simple_arith(verbose = False): display_func(af.atan2(a, b)) c = af.cplx(a) - d = af.cplx(a,b) + d = af.cplx(a, b) display_func(c) display_func(d) display_func(af.real(d)) @@ -183,10 +192,11 @@ def simple_arith(verbose = False): display_func(af.log10(a)) display_func(af.log2(a)) display_func(af.sqrt(a)) + display_func(af.rsqrt(a)) display_func(af.cbrt(a)) - a = af.round(5 * af.randu(3,3) - 1) - b = af.round(5 * af.randu(3,3) - 1) + a = af.round(5 * af.randu(3, 3) - 1) + b = af.round(5 * af.randu(3, 3) - 1) display_func(af.factorial(a)) display_func(af.tgamma(a)) @@ -197,7 +207,7 @@ def simple_arith(verbose = False): a = af.randu(5, 1) b = af.randu(1, 5) - c = af.broadcast(lambda x,y: x+y, a, b) + c = af.broadcast(lambda x, y: x+y, a, b) display_func(a) display_func(b) display_func(c) @@ -208,4 +218,5 @@ def test_add(aa, bb): display_func(test_add(a, b)) -_util.tests['arith'] = simple_arith + +_util.tests["arith"] = simple_arith diff --git a/tests/simple/array_test.py b/tests/simple/array_test.py index 32c042063..b2a787940 100644 --- a/tests/simple/array_test.py +++ b/tests/simple/array_test.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -8,16 +9,22 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## -import arrayfire as af import array as host + +import arrayfire as af + from . import _util + def simple_array(verbose=False): display_func = _util.display_func(verbose) - print_func = _util.print_func(verbose) + print_func = _util.print_func(verbose) a = af.Array([1, 2, 3]) display_func(a) + display_func(a.T) + display_func(a.H) + print_func(a.shape) b = a.as_type(af.Dtype.s32) display_func(b) @@ -27,15 +34,14 @@ def simple_array(verbose=False): print_func(a.is_complex(), a.is_real(), a.is_double(), a.is_single()) print_func(a.is_real_floating(), a.is_floating(), a.is_integer(), a.is_bool()) - - a = af.Array(host.array('i', [4, 5, 6])) + a = af.Array(host.array("i", [4, 5, 6])) display_func(a) print_func(a.elements(), a.type(), a.dims(), a.numdims()) print_func(a.is_empty(), a.is_scalar(), a.is_column(), a.is_row()) print_func(a.is_complex(), a.is_real(), a.is_double(), a.is_single()) print_func(a.is_real_floating(), a.is_floating(), a.is_integer(), a.is_bool()) - a = af.Array(host.array('l', [7, 8, 9] * 3), (3,3)) + a = af.Array(host.array("I", [7, 8, 9] * 3), (3, 3)) display_func(a) print_func(a.elements(), a.type(), a.dims(), a.numdims()) print_func(a.is_empty(), a.is_scalar(), a.is_column(), a.is_row()) @@ -46,7 +52,7 @@ def simple_array(verbose=False): for n in range(a.elements()): print_func(c[n]) - c,s = a.to_ctype(True, True) + c, s = a.to_ctype(True, True) for n in range(a.elements()): print_func(c[n]) print_func(s) @@ -57,4 +63,7 @@ def simple_array(verbose=False): print_func(arr) print_func(lst) -_util.tests['array'] = simple_array + print_func(a.is_sparse()) + + +_util.tests["array"] = simple_array diff --git a/tests/simple/blas.py b/tests/simple/blas.py index fd58d18d9..f04049a93 100644 --- a/tests/simple/blas.py +++ b/tests/simple/blas.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -9,19 +10,21 @@ ######################################################## import arrayfire as af + from . import _util + def simple_blas(verbose=False): display_func = _util.display_func(verbose) - print_func = _util.print_func(verbose) - a = af.randu(5,5) - b = af.randu(5,5) + a = af.randu(5, 5) + b = af.randu(5, 5) + + display_func(af.matmul(a, b)) + display_func(af.matmul(a, b, af.MATPROP.TRANS)) + display_func(af.matmul(a, b, af.MATPROP.NONE, af.MATPROP.TRANS)) - display_func(af.matmul(a,b)) - display_func(af.matmul(a,b,af.MATPROP.TRANS)) - display_func(af.matmul(a,b,af.MATPROP.NONE, af.MATPROP.TRANS)) + b = af.randu(5, 1) + display_func(af.dot(b, b)) - b = af.randu(5,1) - display_func(af.dot(b,b)) -_util.tests['blas'] = simple_blas +_util.tests["blas"] = simple_blas diff --git a/tests/simple/data.py b/tests/simple/data.py index 7c95a8194..d091497eb 100644 --- a/tests/simple/data.py +++ b/tests/simple/data.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -9,30 +10,21 @@ ######################################################## import arrayfire as af + from . import _util + def simple_data(verbose=False): display_func = _util.display_func(verbose) - print_func = _util.print_func(verbose) - display_func(af.constant(100, 3,3, dtype=af.Dtype.f32)) - display_func(af.constant(25, 3,3, dtype=af.Dtype.c32)) - display_func(af.constant(2**50, 3,3, dtype=af.Dtype.s64)) - display_func(af.constant(2+3j, 3,3)) - display_func(af.constant(3+5j, 3,3, dtype=af.Dtype.c32)) + display_func(af.constant(100, 3, 3, dtype=af.Dtype.f32)) + display_func(af.constant(25, 3, 3, dtype=af.Dtype.c32)) + display_func(af.constant(2**50, 3, 3, dtype=af.Dtype.s64)) + display_func(af.constant(2+3j, 3, 3)) + display_func(af.constant(3+5j, 3, 3, dtype=af.Dtype.c32)) display_func(af.range(3, 3)) - display_func(af.iota(3, 3, tile_dims=(2,2))) - - display_func(af.randu(3, 3, 1, 2)) - display_func(af.randu(3, 3, 1, 2, af.Dtype.b8)) - display_func(af.randu(3, 3, dtype=af.Dtype.c32)) - - display_func(af.randn(3, 3, 1, 2)) - display_func(af.randn(3, 3, dtype=af.Dtype.c32)) - - af.set_seed(1024) - assert(af.get_seed() == 1024) + display_func(af.iota(3, 3, tile_dims=(2, 2))) display_func(af.identity(3, 3, 1, 2, af.Dtype.b8)) display_func(af.identity(3, 3, dtype=af.Dtype.c32)) @@ -45,15 +37,14 @@ def simple_data(verbose=False): display_func(b) display_func(c) - display_func(af.diag(b, extract = False)) - display_func(af.diag(c, 1, extract = False)) + display_func(af.diag(b, extract=False)) + display_func(af.diag(c, 1, extract=False)) display_func(af.join(0, a, a)) display_func(af.join(1, a, a, a)) display_func(af.tile(a, 2, 2)) - display_func(af.reorder(a, 1, 0)) display_func(af.shift(a, -1, 1)) @@ -71,7 +62,7 @@ def simple_data(verbose=False): display_func(af.upper(a, False)) display_func(af.upper(a, True)) - a = af.randu(5,5) + a = af.randu(5, 5) display_func(af.transpose(a)) af.transpose_inplace(a) display_func(a) @@ -81,4 +72,6 @@ def simple_data(verbose=False): af.replace(a, a > 0.3, -0.3) display_func(a) -_util.tests['data'] = simple_data + display_func(af.pad(a, (1, 1, 0, 0), (2, 2, 0, 0))) + +_util.tests["data"] = simple_data diff --git a/tests/simple/device.py b/tests/simple/device.py index 98a2b710a..f677c5e2a 100644 --- a/tests/simple/device.py +++ b/tests/simple/device.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -9,11 +10,13 @@ ######################################################## import arrayfire as af + from . import _util + def simple_device(verbose=False): display_func = _util.display_func(verbose) - print_func = _util.print_func(verbose) + print_func = _util.print_func(verbose) print_func(af.device_info()) print_func(af.get_device_count()) print_func(af.is_dbl_supported()) @@ -35,18 +38,40 @@ def simple_device(verbose=False): a = af.randu(100, 100) af.sync(dev) mem_info = af.device_mem_info() - assert(mem_info['alloc']['buffers'] == 1 + mem_info_old['alloc']['buffers']) - assert(mem_info[ 'lock']['buffers'] == 1 + mem_info_old[ 'lock']['buffers']) + assert(mem_info["alloc"]["buffers"] == 1 + mem_info_old["alloc"]["buffers"]) + assert(mem_info["lock"]["buffers"] == 1 + mem_info_old["lock"]["buffers"]) af.set_device(curr_dev) - a = af.randu(10,10) + a = af.randu(10, 10) display_func(a) dev_ptr = af.get_device_ptr(a) print_func(dev_ptr) b = af.Array(src=dev_ptr, dims=a.dims(), dtype=a.dtype(), is_device=True) display_func(b) - af.lock_device_ptr(b) - af.unlock_device_ptr(b) -_util.tests['device'] = simple_device + c = af.randu(10, 10) + af.lock_array(c) + af.unlock_array(c) + + a = af.constant(1, 3, 3) + b = af.constant(2, 3, 3) + af.eval(a) + af.eval(b) + print_func(a) + print_func(b) + c = a + b + d = a - b + af.eval(c, d) + print_func(c) + print_func(d) + + print_func(af.set_manual_eval_flag(True)) + assert(af.get_manual_eval_flag()) + print_func(af.set_manual_eval_flag(False)) + assert(not af.get_manual_eval_flag()) + + display_func(af.is_locked_array(a)) + + +_util.tests["device"] = simple_device diff --git a/tests/simple/image.py b/tests/simple/image.py index 7fdfa90df..6f2e12186 100644 --- a/tests/simple/image.py +++ b/tests/simple/image.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -9,23 +10,24 @@ ######################################################## import arrayfire as af + from . import _util -def simple_image(verbose = False): + +def simple_image(verbose=False): display_func = _util.display_func(verbose) - print_func = _util.print_func(verbose) a = 10 * af.randu(6, 6) - a3 = 10 * af.randu(5,5,3) + a3 = 10 * af.randu(5, 5, 3) - dx,dy = af.gradient(a) + dx, dy = af.gradient(a) display_func(dx) display_func(dy) display_func(af.resize(a, scale=0.5)) display_func(af.resize(a, odim0=8, odim1=8)) - t = af.randu(3,2) + t = af.randu(3, 2) display_func(af.transform(a, t)) display_func(af.rotate(a, 3.14)) display_func(af.translate(a, 1, 1)) @@ -49,11 +51,16 @@ def simple_image(verbose = False): display_func(af.maxfilt(a)) display_func(af.regions(af.round(a) > 3)) + display_func(af.confidenceCC(af.randu(10, 10), + (af.randu(2) * 9).as_type(af.Dtype.u32), (af.randu(2) * 9).as_type(af.Dtype.u32), 3, 3, 10, 0.1)) + - dx,dy = af.sobel_derivatives(a) + dx, dy = af.sobel_derivatives(a) display_func(dx) display_func(dy) display_func(af.sobel_filter(a)) + display_func(af.gaussian_kernel(3, 3)) + display_func(af.gaussian_kernel(3, 3, 1, 1)) ac = af.gray2rgb(a) display_func(ac) @@ -64,7 +71,7 @@ def simple_image(verbose = False): display_func(af.color_space(a, af.CSPACE.RGB, af.CSPACE.GRAY)) - a = af.randu(6,6) + a = af.randu(6, 6) b = af.unwrap(a, 2, 2, 2, 2) c = af.wrap(b, 6, 6, 2, 2, 2, 2) display_func(a) @@ -72,8 +79,21 @@ def simple_image(verbose = False): display_func(c) display_func(af.sat(a)) - a = af.randu(10,10,3) + a = af.randu(10, 10, 3) display_func(af.rgb2ycbcr(a)) display_func(af.ycbcr2rgb(a)) -_util.tests['image'] = simple_image + a = af.randu(10, 10) + b = af.canny(a, low_threshold=0.2, high_threshold=0.8) + + display_func(af.anisotropic_diffusion(a, 0.125, 1.0, 64, af.FLUX.QUADRATIC, af.DIFFUSION.GRAD)) + + a = af.randu(10, 10) + psf = af.gaussian_kernel(3, 3) + cimg = af.convolve(a, psf) + display_func(af.iterativeDeconv(cimg, psf, 100, 0.5, af.ITERATIVE_DECONV.LANDWEBER)) + display_func(af.iterativeDeconv(cimg, psf, 100, 0.5, af.ITERATIVE_DECONV.RICHARDSONLUCY)) + display_func(af.inverseDeconv(cimg, psf, 1.0, af.INVERSE_DECONV.TIKHONOV)) + + +_util.tests["image"] = simple_image diff --git a/tests/simple/index.py b/tests/simple/index.py index 7ebde6eb3..8bb4b571a 100644 --- a/tests/simple/index.py +++ b/tests/simple/index.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -7,14 +8,17 @@ # The complete license agreement can be obtained at: # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## + +import array as host + import arrayfire as af from arrayfire import ParallelRange -import array as host + from . import _util + def simple_index(verbose=False): display_func = _util.display_func(verbose) - print_func = _util.print_func(verbose) a = af.randu(5, 5) display_func(a) b = af.Array(a) @@ -22,16 +26,16 @@ def simple_index(verbose=False): c = a.copy() display_func(c) - display_func(a[0,0]) + display_func(a[0, 0]) display_func(a[0]) display_func(a[:]) - display_func(a[:,:]) - display_func(a[0:3,]) - display_func(a[-2:-1,-1]) + display_func(a[:, :]) + display_func(a[0:3, ]) + display_func(a[-2:-1, -1]) display_func(a[0:5]) display_func(a[0:5:2]) - idx = af.Array(host.array('i', [0, 3, 2])) + idx = af.Array(host.array("i", [0, 3, 2])) display_func(idx) aa = a[idx] display_func(aa) @@ -40,42 +44,42 @@ def simple_index(verbose=False): display_func(a) a[0] = af.randu(1, 5) display_func(a) - a[:] = af.randu(5,5) + a[:] = af.randu(5, 5) display_func(a) - a[:,-1] = af.randu(5,1) + a[:, -1] = af.randu(5, 1) display_func(a) a[0:5:2] = af.randu(3, 5) display_func(a) - a[idx, idx] = af.randu(3,3) + a[idx, idx] = af.randu(3, 3) display_func(a) - - a = af.randu(5,1) - b = af.randu(5,1) + a = af.randu(5, 1) + b = af.randu(5, 1) display_func(a) display_func(b) - for ii in ParallelRange(1,3): + for ii in ParallelRange(1, 3): a[ii] = b[ii] display_func(a) - for ii in ParallelRange(2,5): + for ii in ParallelRange(2, 5): b[ii] = 2 display_func(b) - a = af.randu(3,2) + a = af.randu(3, 2) rows = af.constant(0, 1, dtype=af.Dtype.s32) - b = a[:,rows] + b = a[:, rows] display_func(b) - for r in rows: + for r in range(rows.elements()): display_func(r) - display_func(b[:,r]) + display_func(b[:, r]) a = af.randu(3) c = af.randu(3) - b = af.constant(1,3,dtype=af.Dtype.b8) + b = af.constant(1, 3, dtype=af.Dtype.b8) display_func(a) a[b] = c display_func(a) -_util.tests['index'] = simple_index + +_util.tests["index"] = simple_index diff --git a/tests/simple/interop.py b/tests/simple/interop.py new file mode 100644 index 000000000..f07e6a770 --- /dev/null +++ b/tests/simple/interop.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2015, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +import arrayfire as af + +from . import _util + + +def simple_interop(verbose=False): + if af.AF_NUMPY_FOUND: + import numpy as np + n = np.random.random((5,)) + a = af.to_array(n) + n2 = np.array(a) + assert((n == n2).all()) + n2[:] = 0 + a.to_ndarray(n2) + assert((n == n2).all()) + + n = np.random.random((5, 3)) + a = af.to_array(n) + n2 = np.array(a) + assert((n == n2).all()) + n2[:] = 0 + a.to_ndarray(n2) + assert((n == n2).all()) + + n = np.random.random((5, 3, 2)) + a = af.to_array(n) + n2 = np.array(a) + assert((n == n2).all()) + n2[:] = 0 + a.to_ndarray(n2) + assert((n == n2).all()) + + n = np.random.random((5, 3, 2, 2)) + a = af.to_array(n) + n2 = np.array(a) + assert((n == n2).all()) + n2[:] = 0 + a.to_ndarray(n2) + assert((n == n2).all()) + + if af.AF_PYCUDA_FOUND and af.get_active_backend() == "cuda": + import pycuda.gpuarray as cudaArray + n = np.random.random((5,)) + c = cudaArray.to_gpu(n) + a = af.to_array(c) + n2 = np.array(a) + assert((n == n2).all()) + + n = np.random.random((5, 3)) + c = cudaArray.to_gpu(n) + a = af.to_array(c) + n2 = np.array(a) + assert((n == n2).all()) + + n = np.random.random((5, 3, 2)) + c = cudaArray.to_gpu(n) + a = af.to_array(c) + n2 = np.array(a) + assert((n == n2).all()) + + n = np.random.random((5, 3, 2, 2)) + c = cudaArray.to_gpu(n) + a = af.to_array(c) + n2 = np.array(a) + assert((n == n2).all()) + + if af.AF_PYOPENCL_FOUND and af.backend.name() == "opencl": + # TODO: This needs fixing upstream + # https://github.com/arrayfire/arrayfire/issues/1728 + + # import pyopencl as cl + # import pyopencl.array as clArray + # ctx = cl.create_some_context() + # queue = cl.CommandQueue(ctx) + + # n = np.random.random((5,)) + # c = cl.array.to_device(queue, n) + # a = af.to_array(c) + # n2 = np.array(a) + # assert((n==n2).all()) + + # n = np.random.random((5,3)) + # c = cl.array.to_device(queue, n) + # a = af.to_array(c) + # n2 = np.array(a) + # assert((n==n2).all()) + + # n = np.random.random((5,3,2)) + # c = cl.array.to_device(queue, n) + # a = af.to_array(c) + # n2 = np.array(a) + # assert((n==n2).all()) + + # n = np.random.random((5,3,2,2)) + # c = cl.array.to_device(queue, n) + # a = af.to_array(c) + # n2 = np.array(a) + # assert((n==n2).all()) + pass + + if af.AF_NUMBA_FOUND and af.get_active_backend() == "cuda": + from numba import cuda + + n = np.random.random((5,)) + c = cuda.to_device(n) + a = af.to_array(c) + n2 = np.array(a) + assert((n == n2).all()) + + n = np.random.random((5, 3)) + c = cuda.to_device(n) + a = af.to_array(c) + n2 = np.array(a) + assert((n == n2).all()) + + n = np.random.random((5, 3, 2)) + c = cuda.to_device(n) + a = af.to_array(c) + n2 = np.array(a) + assert((n == n2).all()) + + n = np.random.random((5, 3, 2, 2)) + c = cuda.to_device(n) + a = af.to_array(c) + n2 = np.array(a) + assert((n == n2).all()) + + +_util.tests["interop"] = simple_interop diff --git a/tests/simple/lapack.py b/tests/simple/lapack.py index 8ff59c2b5..8cd3e9ac3 100644 --- a/tests/simple/lapack.py +++ b/tests/simple/lapack.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -7,15 +8,18 @@ # The complete license agreement can be obtained at: # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## + import arrayfire as af + from . import _util + def simple_lapack(verbose=False): display_func = _util.display_func(verbose) - print_func = _util.print_func(verbose) - a = af.randu(5,5) + print_func = _util.print_func(verbose) + a = af.randu(5, 5) - l,u,p = af.lu(a) + l, u, p = af.lu(a) display_func(l) display_func(u) @@ -26,9 +30,9 @@ def simple_lapack(verbose=False): display_func(a) display_func(p) - a = af.randu(5,3) + a = af.randu(5, 3) - q,r,t = af.qr(a) + q, r, t = af.qr(a) display_func(q) display_func(r) @@ -39,21 +43,24 @@ def simple_lapack(verbose=False): display_func(a) a = af.randu(5, 5) - a = af.matmulTN(a, a) + 10 * af.identity(5,5) + a = af.matmulTN(a, a.copy()) + 10 * af.identity(5, 5) - R,info = af.cholesky(a) + R, info = af.cholesky(a) display_func(R) print_func(info) af.cholesky_inplace(a) display_func(a) - a = af.randu(5,5) + a = af.randu(5, 5) ai = af.inverse(a) display_func(a) display_func(ai) + ai = af.pinverse(a) + display_func(ai) + x0 = af.randu(5, 3) b = af.matmul(a, x0) x1 = af.solve(a, b) @@ -74,11 +81,12 @@ def simple_lapack(verbose=False): print_func(af.norm(a, af.NORM.MATRIX_INF)) print_func(af.norm(a, af.NORM.MATRIX_L_PQ, 1, 1)) - a = af.randu(10,10) + a = af.randu(10, 10) display_func(a) - u,s,vt = af.svd(a) + u, s, vt = af.svd(a) display_func(af.matmul(af.matmul(u, af.diag(s, 0, False)), vt)) - u,s,vt = af.svd_inplace(a) + u, s, vt = af.svd_inplace(a) display_func(af.matmul(af.matmul(u, af.diag(s, 0, False)), vt)) -_util.tests['lapack'] = simple_lapack + +_util.tests["lapack"] = simple_lapack diff --git a/tests/simple/random.py b/tests/simple/random.py new file mode 100644 index 000000000..5152cb4e0 --- /dev/null +++ b/tests/simple/random.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2015, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +import arrayfire as af + +from . import _util + + +def simple_random(verbose=False): + display_func = _util.display_func(verbose) + + display_func(af.randu(3, 3, 1, 2)) + display_func(af.randu(3, 3, 1, 2, af.Dtype.b8)) + display_func(af.randu(3, 3, dtype=af.Dtype.c32)) + + display_func(af.randn(3, 3, 1, 2)) + display_func(af.randn(3, 3, dtype=af.Dtype.c32)) + + af.set_seed(1024) + assert(af.get_seed() == 1024) + + engine = af.Random_Engine(af.RANDOM_ENGINE.MERSENNE_GP11213, 100) + + display_func(af.randu(3, 3, 1, 2, engine=engine)) + display_func(af.randu(3, 3, 1, 2, af.Dtype.s32, engine=engine)) + display_func(af.randu(3, 3, dtype=af.Dtype.c32, engine=engine)) + + display_func(af.randn(3, 3, engine=engine)) + engine.set_seed(100) + assert(engine.get_seed() == 100) + + +_util.tests["random"] = simple_random diff --git a/tests/simple/signal.py b/tests/simple/signal.py index 03d89b4a8..0e3e8da9d 100644 --- a/tests/simple/signal.py +++ b/tests/simple/signal.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -9,21 +10,25 @@ ######################################################## import arrayfire as af + from . import _util + def simple_signal(verbose=False): display_func = _util.display_func(verbose) - print_func = _util.print_func(verbose) - a = af.randu(10, 1) - pos0 = af.randu(10) * 10 - display_func(af.approx1(a, pos0)) + signal = af.randu(10) + x_new = af.randu(10) + x_orig = af.randu(10) + display_func(af.approx1(signal, x_new, xp=x_orig)) - a = af.randu(3, 3) - pos0 = af.randu(3, 3) * 10 - pos1 = af.randu(3, 3) * 10 + signal = af.randu(3, 3) + x_new = af.randu(3, 3) + x_orig = af.randu(3, 3) + y_new = af.randu(3, 3) + y_orig = af.randu(3, 3) - display_func(af.approx2(a, pos0, pos1)) + display_func(af.approx2(signal, x_new, y_new, xp=x_orig, yp=y_orig)) a = af.randu(8, 1) display_func(a) @@ -96,6 +101,13 @@ def simple_signal(verbose=False): display_func(af.convolve(a, b)) display_func(af.fft_convolve(a, b)) + c = af.convolve2NN(a, b) + display_func(c) + in_dims = c.dims() + incoming_grad = af.constant(1, in_dims[0], in_dims[1]); + g = af.convolve2GradientNN(incoming_grad, a, b, c) + display_func(g) + a = af.randu(5, 5, 3) b = af.randu(3, 3, 2) display_func(af.convolve3(a, b)) @@ -103,11 +115,15 @@ def simple_signal(verbose=False): display_func(af.convolve(a, b)) display_func(af.fft_convolve(a, b)) - b = af.randu(3, 1) x = af.randu(10, 1) a = af.randu(2, 1) display_func(af.fir(b, x)) display_func(af.iir(b, a, x)) -_util.tests['signal'] = simple_signal + display_func(af.medfilt1(a)) + display_func(af.medfilt2(a)) + display_func(af.medfilt(a)) + + +_util.tests["signal"] = simple_signal diff --git a/tests/simple/sparse.py b/tests/simple/sparse.py new file mode 100644 index 000000000..bda87dfb7 --- /dev/null +++ b/tests/simple/sparse.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2015, ArrayFire +# All rights reserved. +# +# This file is distributed under 3-clause BSD license. +# The complete license agreement can be obtained at: +# http://arrayfire.com/licenses/BSD-3-Clause +######################################################## + +import arrayfire as af + +from . import _util + + +def simple_sparse(verbose=False): + display_func = _util.display_func(verbose) + print_func = _util.print_func(verbose) + + dd = af.randu(5, 5) + ds = dd * (dd > 0.5) + sp = af.create_sparse_from_dense(ds) + display_func(af.sparse_get_info(sp)) + display_func(af.sparse_get_values(sp)) + display_func(af.sparse_get_row_idx(sp)) + display_func(af.sparse_get_col_idx(sp)) + print_func(af.sparse_get_nnz(sp)) + print_func(af.sparse_get_storage(sp)) + + +_util.tests["sparse"] = simple_sparse diff --git a/tests/simple/statistics.py b/tests/simple/statistics.py index 2b9099849..39fe6703f 100644 --- a/tests/simple/statistics.py +++ b/tests/simple/statistics.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -9,11 +10,13 @@ ######################################################## import arrayfire as af + from . import _util + def simple_statistics(verbose=False): display_func = _util.display_func(verbose) - print_func = _util.print_func(verbose) + print_func = _util.print_func(verbose) a = af.randu(5, 5) b = af.randu(5, 5) @@ -25,23 +28,40 @@ def simple_statistics(verbose=False): print_func(af.mean(a, weights=w)) display_func(af.var(a, dim=0)) - display_func(af.var(a, isbiased=True, dim=0)) + display_func(af.var(a, bias=af.VARIANCE.SAMPLE, dim=0)) display_func(af.var(a, weights=w, dim=0)) print_func(af.var(a)) - print_func(af.var(a, isbiased=True)) + print_func(af.var(a, bias=af.VARIANCE.SAMPLE)) print_func(af.var(a, weights=w)) + mean, var = af.meanvar(a, dim=0) + display_func(mean) + display_func(var) + mean, var = af.meanvar(a, weights=w, bias=af.VARIANCE.SAMPLE, dim=0) + display_func(mean) + display_func(var) + display_func(af.stdev(a, dim=0)) print_func(af.stdev(a)) display_func(af.var(a, dim=0)) - display_func(af.var(a, isbiased=True, dim=0)) + display_func(af.var(a, bias=af.VARIANCE.SAMPLE, dim=0)) print_func(af.var(a)) - print_func(af.var(a, isbiased=True)) + print_func(af.var(a, bias=af.VARIANCE.SAMPLE)) display_func(af.median(a, dim=0)) print_func(af.median(w)) print_func(af.corrcoef(a, b)) -_util.tests['statistics'] = simple_statistics + data = af.iota(5, 3) + k = 3 + dim = 0 + order = af.TOPK.DEFAULT # defaults to af.TOPK.MAX + assert(dim == 0) # topk currently supports first dim only + values, indices = af.topk(data, k, dim, order) + display_func(values) + display_func(indices) + + +_util.tests["statistics"] = simple_statistics