From 988f432b287421f7075e24258bb33472aabc807b Mon Sep 17 00:00:00 2001 From: Miguel Lloreda Date: Mon, 22 Oct 2018 09:13:58 -0500 Subject: [PATCH 01/38] Fixed issue with create_sparse_from_host - (issue #189). Casted row and column index arrays to be of s32 type prior to passing in to lib call. Previously failing due to failing assertions. --- arrayfire/sparse.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arrayfire/sparse.py b/arrayfire/sparse.py index b261bdbd8..91b8f7a23 100644 --- a/arrayfire/sparse.py +++ b/arrayfire/sparse.py @@ -90,7 +90,10 @@ def create_sparse_from_host(values, row_idx, col_idx, nrows, ncols, storage = ST A sparse matrix. """ - return create_sparse(to_array(values), to_array(row_idx), to_array(col_idx), nrows, ncols, storage) + 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): """ From 9cea077468b5ca15751ca25f6dd23a3e7265a55e Mon Sep 17 00:00:00 2001 From: Umar Arshad Date: Sun, 23 Dec 2018 23:25:03 -0500 Subject: [PATCH 02/38] Workaround failure in bench_cg. Evaluate x array early --- examples/benchmarks/bench_cg.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/benchmarks/bench_cg.py b/examples/benchmarks/bench_cg.py index 8a74f25a9..33c5b856c 100644 --- a/examples/benchmarks/bench_cg.py +++ b/examples/benchmarks/bench_cg.py @@ -80,6 +80,7 @@ def calc_arrayfire(A, b, x0, maxiter=10): 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) @@ -137,11 +138,11 @@ def timeit(calc, iters, args): def test(): print("\nTesting benchmark functions...") - A, b, x0 = setup_input(50) # dense A + 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-6): + if af.sum(af.abs(x1 - x2)/x2 > 1e-5): raise ValueError("arrayfire test failed") if np: An = to_numpy(A) @@ -162,11 +163,13 @@ def test(): 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)) @@ -192,9 +195,8 @@ def bench(n=4*1024, sparsity=7, maxiter=10, iters=10): if (len(sys.argv) > 1): af.set_device(int(sys.argv[1])) - af.info() - + af.info() test() - + for n in (128, 256, 512, 1024, 2048, 4096): bench(n) From 2a48a39f0cb936af3d6e8de96b55fccd14cabba2 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Tue, 20 Aug 2019 01:43:30 -0700 Subject: [PATCH 03/38] Fix a documentation issue --- arrayfire/array.py | 1 + 1 file changed, 1 insertion(+) diff --git a/arrayfire/array.py b/arrayfire/array.py index 76afece01..801cd502b 100644 --- a/arrayfire/array.py +++ b/arrayfire/array.py @@ -563,6 +563,7 @@ def device_ptr(self): 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. From 632cb8f102f4aea2b534bf8b42e53906214c7120 Mon Sep 17 00:00:00 2001 From: Anton Chernyatevich Date: Wed, 4 Sep 2019 03:47:55 +0300 Subject: [PATCH 04/38] Move tests to root dir. Minor setup refactoring --- arrayfire/tests/simple/__init__.py | 24 ------- arrayfire/tests/simple_tests.py | 27 ------- setup.cfg | 27 +++++++ setup.py | 19 ++--- {arrayfire/tests => tests}/__init__.py | 2 + {arrayfire/tests => tests}/__main__.py | 23 +++--- tests/simple/__init__.py | 44 ++++++++++++ {arrayfire/tests => tests}/simple/_util.py | 23 +++--- .../tests => tests}/simple/algorithm.py | 35 ++++----- {arrayfire/tests => tests}/simple/arith.py | 34 ++++----- .../tests => tests}/simple/array_test.py | 20 +++--- {arrayfire/tests => tests}/simple/blas.py | 23 +++--- {arrayfire/tests => tests}/simple/data.py | 28 ++++---- {arrayfire/tests => tests}/simple/device.py | 22 +++--- {arrayfire/tests => tests}/simple/image.py | 25 ++++--- {arrayfire/tests => tests}/simple/index.py | 46 ++++++------ {arrayfire/tests => tests}/simple/interop.py | 71 ++++++++++--------- {arrayfire/tests => tests}/simple/lapack.py | 31 ++++---- {arrayfire/tests => tests}/simple/random.py | 9 ++- {arrayfire/tests => tests}/simple/signal.py | 20 +++--- {arrayfire/tests => tests}/simple/sparse.py | 10 ++- .../tests => tests}/simple/statistics.py | 16 +++-- 22 files changed, 322 insertions(+), 257 deletions(-) delete mode 100644 arrayfire/tests/simple/__init__.py delete mode 100755 arrayfire/tests/simple_tests.py create mode 100644 setup.cfg rename {arrayfire/tests => tests}/__init__.py (93%) rename {arrayfire/tests => tests}/__main__.py (75%) create mode 100644 tests/simple/__init__.py rename {arrayfire/tests => tests}/simple/_util.py (88%) rename {arrayfire/tests => tests}/simple/algorithm.py (74%) rename {arrayfire/tests => tests}/simple/arith.py (89%) rename {arrayfire/tests => tests}/simple/array_test.py (88%) rename {arrayfire/tests => tests}/simple/blas.py (56%) rename {arrayfire/tests => tests}/simple/data.py (72%) rename {arrayfire/tests => tests}/simple/device.py (79%) rename {arrayfire/tests => tests}/simple/image.py (84%) rename {arrayfire/tests => tests}/simple/index.py (69%) rename {arrayfire/tests => tests}/simple/interop.py (68%) rename {arrayfire/tests => tests}/simple/lapack.py (78%) rename {arrayfire/tests => tests}/simple/random.py (91%) rename {arrayfire/tests => tests}/simple/signal.py (89%) rename {arrayfire/tests => tests}/simple/sparse.py (88%) rename {arrayfire/tests => tests}/simple/statistics.py (82%) diff --git a/arrayfire/tests/simple/__init__.py b/arrayfire/tests/simple/__init__.py deleted file mode 100644 index 26ed88961..000000000 --- a/arrayfire/tests/simple/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -####################################################### -# 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 -######################################################## - -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 .interop import * -from .lapack import * -from .signal import * -from .statistics import * -from .random import * -from .sparse import * -from ._util import tests diff --git a/arrayfire/tests/simple_tests.py b/arrayfire/tests/simple_tests.py deleted file mode 100755 index 603ae9f37..000000000 --- a/arrayfire/tests/simple_tests.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/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 -######################################################## - -from __future__ import absolute_import - -from . import simple -import sys - -if __name__ == "__main__": - verbose = False - - if len(sys.argv) > 1: - verbose = int(sys.argv[1]) - - test_list = None - if len(sys.argv) > 2: - test_list = sys.argv[2:] - - simple.tests.run(test_list, verbose) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..6b0fb031d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,27 @@ +[metadata] +name = arrayfire +version = 3.6.20181017 +description = Python bindings for ArrayFire +licence = BSD +long_description = file: README.md +maintainer = Pavan Yalamanchili +maintainer_email = contact@pavanky.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: + +[options.packages.find] +exclude = + examples + tests + +[flake8] +application-import-names = arrayfire +import-order-style = pep8 +max-line-length = 119 diff --git a/setup.py b/setup.py index 92900cc90..e4d485c30 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,8 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## -from setuptools import setup, find_packages -#from __af_version__ import full_version +# TODO: Look for af libraries during setup -#TODO: -#1) Look for af libraries during setup +from setuptools import setup -setup( - author="Pavan Yalamanchili", - author_email="contact@pavanky.com", - name="arrayfire", - version="3.6.20181017", - description="Python bindings for ArrayFire", - license="BSD", - url="http://arrayfire.com", - packages=find_packages() -) +setup() diff --git a/arrayfire/tests/__init__.py b/tests/__init__.py similarity index 93% rename from arrayfire/tests/__init__.py rename to tests/__init__.py index be7669881..24e9ac759 100644 --- a/arrayfire/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. diff --git a/arrayfire/tests/__main__.py b/tests/__main__.py similarity index 75% rename from arrayfire/tests/__main__.py rename to tests/__main__.py index 468396566..6bed94278 100644 --- a/arrayfire/tests/__main__.py +++ b/tests/__main__.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -10,35 +12,38 @@ 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 new file mode 100644 index 000000000..4950136e3 --- /dev/null +++ b/tests/simple/__init__.py @@ -0,0 +1,44 @@ +#!/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 +######################################################## + +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/arrayfire/tests/simple/_util.py b/tests/simple/_util.py similarity index 88% rename from arrayfire/tests/simple/_util.py rename to tests/simple/_util.py index cda7c84a8..2275cf2fc 100644 --- a/arrayfire/tests/simple/_util.py +++ b/tests/simple/_util.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -6,13 +8,13 @@ # The complete license agreement can be obtained at: # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## -import traceback + import logging -import arrayfire as af import sys +import traceback -class _simple_test_dict(dict): +class _simple_test_dict(dict): def __init__(self): self.print_str = "Simple %16s: %s" self.failed = False @@ -21,7 +23,7 @@ def __init__(self): 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 = '' + self.print_log = "" try: test = self[key] except KeyError: @@ -31,27 +33,30 @@ def run(self, name_list=None, verbose=False): try: test(verbose) print(self.print_str % (key, "PASSED")) - except Exception as e: + except Exception: print(self.print_str % (key, "FAILED")) self.failed = True - if (not verbose): + if not verbose: print(tests.print_log) logging.error(traceback.format_exc()) - if (self.failed): + if self.failed: sys.exit(1) + tests = _simple_test_dict() + def print_func(verbose): def print_func_impl(*args): - _print_log = '' + _print_log = "" for arg in args: _print_log += str(arg) + '\n' - if (verbose): + 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/arrayfire/tests/simple/algorithm.py b/tests/simple/algorithm.py similarity index 74% rename from arrayfire/tests/simple/algorithm.py rename to tests/simple/algorithm.py index 5528da3b0..5b40d6916 100644 --- a/arrayfire/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,18 +10,19 @@ ######################################################## 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)) @@ -58,27 +60,27 @@ def simple_algorithm(verbose = 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) @@ -90,4 +92,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/arrayfire/tests/simple/arith.py b/tests/simple/arith.py similarity index 89% rename from arrayfire/tests/simple/arith.py rename to tests/simple/arith.py index 84c291aec..306b93fff 100644 --- a/arrayfire/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,13 +10,14 @@ ######################################################## 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) + 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,7 +100,7 @@ def simple_arith(verbose = False): display_func(a == 0.5) display_func(0.5 == a) - a = af.randu(3,3,dtype=af.Dtype.u32) + a = af.randu(3, 3, dtype=af.Dtype.u32) b = af.constant(4, 3, 3, dtype=af.Dtype.u32) display_func(a & b) @@ -132,17 +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.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)) - display_func(af.rem(a,b)) + display_func(af.rem(a, b)) - a = af.randu(3,3) - 0.5 - b = af.randu(3,3) - 0.5 + a = af.randu(3, 3) - 0.5 + b = af.randu(3, 3) - 0.5 display_func(af.abs(a)) display_func(af.arg(a)) @@ -161,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)) @@ -193,8 +194,8 @@ def simple_arith(verbose = False): display_func(af.sqrt(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)) @@ -205,7 +206,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) @@ -216,4 +217,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/arrayfire/tests/simple/array_test.py b/tests/simple/array_test.py similarity index 88% rename from arrayfire/tests/simple/array_test.py rename to tests/simple/array_test.py index 0c6ab5262..b2a787940 100644 --- a/arrayfire/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,13 +9,16 @@ # 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) @@ -30,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('I', [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()) @@ -49,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) @@ -62,4 +65,5 @@ def simple_array(verbose=False): print_func(a.is_sparse()) -_util.tests['array'] = simple_array + +_util.tests["array"] = simple_array diff --git a/arrayfire/tests/simple/blas.py b/tests/simple/blas.py similarity index 56% rename from arrayfire/tests/simple/blas.py rename to tests/simple/blas.py index fd58d18d9..f04049a93 100644 --- a/arrayfire/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/arrayfire/tests/simple/data.py b/tests/simple/data.py similarity index 72% rename from arrayfire/tests/simple/data.py rename to tests/simple/data.py index 86b900baf..d80f9e125 100644 --- a/arrayfire/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,20 +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.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)) @@ -35,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)) @@ -61,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) @@ -71,4 +72,5 @@ def simple_data(verbose=False): af.replace(a, a > 0.3, -0.3) display_func(a) -_util.tests['data'] = simple_data + +_util.tests["data"] = simple_data diff --git a/arrayfire/tests/simple/device.py b/tests/simple/device.py similarity index 79% rename from arrayfire/tests/simple/device.py rename to tests/simple/device.py index 279fa3168..f677c5e2a 100644 --- a/arrayfire/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,19 +38,19 @@ 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) - c = af.randu(10,10) + c = af.randu(10, 10) af.lock_array(c) af.unlock_array(c) @@ -64,10 +67,11 @@ def simple_device(verbose=False): print_func(d) print_func(af.set_manual_eval_flag(True)) - assert(af.get_manual_eval_flag() == True) + assert(af.get_manual_eval_flag()) print_func(af.set_manual_eval_flag(False)) - assert(af.get_manual_eval_flag() == False) + assert(not af.get_manual_eval_flag()) display_func(af.is_locked_array(a)) -_util.tests['device'] = simple_device + +_util.tests["device"] = simple_device diff --git a/arrayfire/tests/simple/image.py b/tests/simple/image.py similarity index 84% rename from arrayfire/tests/simple/image.py rename to tests/simple/image.py index 9c7887ebb..1489e94dc 100644 --- a/arrayfire/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)) @@ -50,7 +52,7 @@ def simple_image(verbose = False): display_func(af.regions(af.round(a) > 3)) - dx,dy = af.sobel_derivatives(a) + dx, dy = af.sobel_derivatives(a) display_func(dx) display_func(dy) display_func(af.sobel_filter(a)) @@ -66,7 +68,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) @@ -74,13 +76,14 @@ 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)) a = af.randu(10, 10) - b = af.canny(a, low_threshold = 0.2, high_threshold = 0.8) + 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)) -_util.tests['image'] = simple_image + +_util.tests["image"] = simple_image diff --git a/arrayfire/tests/simple/index.py b/tests/simple/index.py similarity index 69% rename from arrayfire/tests/simple/index.py rename to tests/simple/index.py index 7ebde6eb3..a0b0cc757 100644 --- a/arrayfire/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: 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/arrayfire/tests/simple/interop.py b/tests/simple/interop.py similarity index 68% rename from arrayfire/tests/simple/interop.py rename to tests/simple/interop.py index ee924c6fa..f07e6a770 100644 --- a/arrayfire/tests/simple/interop.py +++ b/tests/simple/interop.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -9,72 +10,73 @@ ######################################################## import arrayfire as af + from . import _util -def simple_interop(verbose = False): + +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()) + assert((n == n2).all()) n2[:] = 0 a.to_ndarray(n2) - assert((n==n2).all()) + assert((n == n2).all()) - n = np.random.random((5,3)) + n = np.random.random((5, 3)) a = af.to_array(n) n2 = np.array(a) - assert((n==n2).all()) + assert((n == n2).all()) n2[:] = 0 a.to_ndarray(n2) - assert((n==n2).all()) + assert((n == n2).all()) - n = np.random.random((5,3,2)) + n = np.random.random((5, 3, 2)) a = af.to_array(n) n2 = np.array(a) - assert((n==n2).all()) + assert((n == n2).all()) n2[:] = 0 a.to_ndarray(n2) - assert((n==n2).all()) + assert((n == n2).all()) - n = np.random.random((5,3,2,2)) + n = np.random.random((5, 3, 2, 2)) a = af.to_array(n) n2 = np.array(a) - assert((n==n2).all()) + assert((n == n2).all()) n2[:] = 0 a.to_ndarray(n2) - assert((n==n2).all()) + assert((n == n2).all()) - if af.AF_PYCUDA_FOUND and af.get_active_backend() == 'cuda': - import pycuda.autoinit + 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()) + assert((n == n2).all()) - n = np.random.random((5,3)) + n = np.random.random((5, 3)) c = cudaArray.to_gpu(n) a = af.to_array(c) n2 = np.array(a) - assert((n==n2).all()) + assert((n == n2).all()) - n = np.random.random((5,3,2)) + 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()) + assert((n == n2).all()) - n = np.random.random((5,3,2,2)) + 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()) + assert((n == n2).all()) - if af.AF_PYOPENCL_FOUND and af.backend.name() == 'opencl': - # This needs fixing upstream + 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 @@ -107,33 +109,32 @@ def simple_interop(verbose = False): # assert((n==n2).all()) pass - if af.AF_NUMBA_FOUND and af.get_active_backend() == 'cuda': - - import numba + 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()) + assert((n == n2).all()) - n = np.random.random((5,3)) + n = np.random.random((5, 3)) c = cuda.to_device(n) a = af.to_array(c) n2 = np.array(a) - assert((n==n2).all()) + assert((n == n2).all()) - n = np.random.random((5,3,2)) + 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()) + assert((n == n2).all()) - n = np.random.random((5,3,2,2)) + 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()) + assert((n == n2).all()) + -_util.tests['interop'] = simple_interop +_util.tests["interop"] = simple_interop diff --git a/arrayfire/tests/simple/lapack.py b/tests/simple/lapack.py similarity index 78% rename from arrayfire/tests/simple/lapack.py rename to tests/simple/lapack.py index e3c3dbbb2..e27fb6bc0 100644 --- a/arrayfire/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,16 +43,16 @@ def simple_lapack(verbose=False): display_func(a) a = af.randu(5, 5) - a = af.matmulTN(a, a.copy()) + 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) @@ -74,11 +78,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/arrayfire/tests/simple/random.py b/tests/simple/random.py similarity index 91% rename from arrayfire/tests/simple/random.py rename to tests/simple/random.py index 544389836..5152cb4e0 100644 --- a/arrayfire/tests/simple/random.py +++ b/tests/simple/random.py @@ -1,4 +1,5 @@ -#!/usr/bin/python +#!/usr/bin/env python + ####################################################### # Copyright (c) 2015, ArrayFire # All rights reserved. @@ -9,11 +10,12 @@ ######################################################## import arrayfire as af + from . import _util + def simple_random(verbose=False): display_func = _util.display_func(verbose) - print_func = _util.print_func(verbose) display_func(af.randu(3, 3, 1, 2)) display_func(af.randu(3, 3, 1, 2, af.Dtype.b8)) @@ -35,4 +37,5 @@ def simple_random(verbose=False): engine.set_seed(100) assert(engine.get_seed() == 100) -_util.tests['random'] = simple_random + +_util.tests["random"] = simple_random diff --git a/arrayfire/tests/simple/signal.py b/tests/simple/signal.py similarity index 89% rename from arrayfire/tests/simple/signal.py rename to tests/simple/signal.py index fa8036dc2..d92526488 100644 --- a/arrayfire/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,24 +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) signal = af.randu(10) - x_new = af.randu(10) + x_new = af.randu(10) x_orig = af.randu(10) - display_func(af.approx1(signal, x_new, xp = x_orig)) + display_func(af.approx1(signal, x_new, xp=x_orig)) signal = af.randu(3, 3) - x_new = af.randu(3, 3) + x_new = af.randu(3, 3) x_orig = af.randu(3, 3) - y_new = af.randu(3, 3) + y_new = af.randu(3, 3) y_orig = af.randu(3, 3) - display_func(af.approx2(signal, x_new, y_new, xp = x_orig, yp = y_orig)) + display_func(af.approx2(signal, x_new, y_new, xp=x_orig, yp=y_orig)) a = af.randu(8, 1) display_func(a) @@ -106,7 +108,6 @@ 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) @@ -117,4 +118,5 @@ def simple_signal(verbose=False): display_func(af.medfilt2(a)) display_func(af.medfilt(a)) -_util.tests['signal'] = simple_signal + +_util.tests["signal"] = simple_signal diff --git a/arrayfire/tests/simple/sparse.py b/tests/simple/sparse.py similarity index 88% rename from arrayfire/tests/simple/sparse.py rename to tests/simple/sparse.py index 89315dfb1..bda87dfb7 100644 --- a/arrayfire/tests/simple/sparse.py +++ b/tests/simple/sparse.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_sparse(verbose=False): display_func = _util.display_func(verbose) - print_func = _util.print_func(verbose) + print_func = _util.print_func(verbose) dd = af.randu(5, 5) ds = dd * (dd > 0.5) @@ -25,4 +28,5 @@ def simple_sparse(verbose=False): print_func(af.sparse_get_nnz(sp)) print_func(af.sparse_get_storage(sp)) -_util.tests['sparse'] = simple_sparse + +_util.tests["sparse"] = simple_sparse diff --git a/arrayfire/tests/simple/statistics.py b/tests/simple/statistics.py similarity index 82% rename from arrayfire/tests/simple/statistics.py rename to tests/simple/statistics.py index d7d5a63d4..2815af335 100644 --- a/arrayfire/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) @@ -47,10 +50,11 @@ def simple_statistics(verbose=False): 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) + 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 + +_util.tests["statistics"] = simple_statistics From 1bd0de5f42f239bb4d1c06bfaec9b72456056ccc Mon Sep 17 00:00:00 2001 From: Anton Chernyatevich Date: Thu, 26 Sep 2019 16:29:23 +0300 Subject: [PATCH 05/38] Fix tests dir creation after package installation --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 6b0fb031d..0e02078f1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,7 @@ classifiers = packages = find: [options.packages.find] +include = arrayfire exclude = examples tests From ec86afa8d4a7e445207b34b94ea8acb13814d377 Mon Sep 17 00:00:00 2001 From: syurkevi Date: Mon, 9 Mar 2020 02:57:28 -0400 Subject: [PATCH 06/38] Add 3.7 features to python wrapper (#221) * adds af_pad to python wrapper * adds meanvar to python wrapper * adds inverse square root to python wrapper * adds pinverse to python wrapper * adds NN convolve and gradient functions to wrapper * adds reduce by key to python wrapper missing convolve gradient function * adds confidenceCC to python wrapper * adds fp16 support to python wrapper * update version * remove stray print statements * adds axes_label_format to python wrapper, removes mistakenly copied code --- __af_version__.py | 4 +- arrayfire/__init__.py | 1 + arrayfire/algorithm.py | 190 +++++++++++++++++++++++++++++++++++++ arrayfire/arith.py | 20 ++++ arrayfire/array.py | 8 ++ arrayfire/data.py | 52 ++++++++++ arrayfire/device.py | 19 ++++ arrayfire/graphics.py | 28 ++++++ arrayfire/image.py | 43 +++++++++ arrayfire/lapack.py | 33 +++++++ arrayfire/library.py | 21 ++++ arrayfire/ml.py | 74 +++++++++++++++ arrayfire/signal.py | 50 ++++++++++ arrayfire/statistics.py | 42 ++++++++ arrayfire/util.py | 12 ++- setup.cfg | 2 +- tests/simple/algorithm.py | 24 +++++ tests/simple/arith.py | 1 + tests/simple/data.py | 1 + tests/simple/image.py | 3 + tests/simple/lapack.py | 3 + tests/simple/signal.py | 5 + tests/simple/statistics.py | 7 ++ 23 files changed, 636 insertions(+), 7 deletions(-) create mode 100644 arrayfire/ml.py diff --git a/__af_version__.py b/__af_version__.py index 9b8a5f40c..dfa78b826 100644 --- a/__af_version__.py +++ b/__af_version__.py @@ -9,6 +9,6 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## -version = "3.5" -release = "20170718" +version = "3.7" +release = "20200213" full_version = version + "." + release diff --git a/arrayfire/__init__.py b/arrayfire/__init__.py index 538085bb3..5cb009951 100644 --- a/arrayfire/__init__.py +++ b/arrayfire/__init__.py @@ -74,6 +74,7 @@ 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 36b5d79b8..d5adbcce5 100644 --- a/arrayfire/algorithm.py +++ b/arrayfire/algorithm.py @@ -44,6 +44,31 @@ def _nan_reduce_all(a, c_func, nan_val): 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,28 @@ def max(a, dim=None): else: return _reduce_all(a, backend.get().af_max_all) +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 +294,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 +338,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 +382,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 diff --git a/arrayfire/arith.py b/arrayfire/arith.py index b0d945f05..e4dc2fdfd 100644 --- a/arrayfire/arith.py +++ b/arrayfire/arith.py @@ -958,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 801cd502b..289ef4699 100644 --- a/arrayfire/array.py +++ b/arrayfire/array.py @@ -783,6 +783,14 @@ def is_single(self): 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. diff --git a/arrayfire/data.py b/arrayfire/data.py index d9ad19e48..1fbe17a53 100644 --- a/arrayfire/data.py +++ b/arrayfire/data.py @@ -799,6 +799,58 @@ def replace(lhs, cond, rhs): else: safe_call(backend.get().af_replace_scalar(lhs.arr, cond.arr, c_double_t(rhs))) +def pad(a, beginPadding, endPadding, padFillType = PAD.ZERO): + """ + Pad an array + + This function will pad an array with the specified border size. + Newly padded values can be filled in several different ways. + + Parameters + ---------- + + a: af.Array + A multi dimensional input arrayfire array. + + beginPadding: tuple of ints. default: (0, 0, 0, 0). + + endPadding: tuple of ints. default: (0, 0, 0, 0). + + padFillType: optional af.PAD default: af.PAD.ZERO + specifies type of values to fill padded border with + + Returns + ------- + output: af.Array + A padded array + + Examples + --------- + >>> import arrayfire as af + >>> a = af.randu(3,3) + >>> af.display(a) + [3 3 1 1] + 0.4107 0.1794 0.3775 + 0.8224 0.4198 0.3027 + 0.9518 0.0081 0.6456 + + >>> padded = af.pad(a, (1, 1), (1, 1), af.ZERO) + >>> af.display(padded) + [5 5 1 1] + 0.0000 0.0000 0.0000 0.0000 0.0000 + 0.0000 0.4107 0.1794 0.3775 0.0000 + 0.0000 0.8224 0.4198 0.3027 0.0000 + 0.0000 0.9518 0.0081 0.6456 0.0000 + 0.0000 0.0000 0.0000 0.0000 0.0000 + """ + out = Array() + begin_dims = dim4(beginPadding[0], beginPadding[1], beginPadding[2], beginPadding[3]) + end_dims = dim4(endPadding[0], endPadding[1], endPadding[2], endPadding[3]) + + safe_call(backend.get().af_pad(c_pointer(out.arr), a.arr, 4, c_pointer(begin_dims), 4, c_pointer(end_dims), padFillType.value)) + return out + + def lookup(a, idx, dim=0): """ Lookup the values of input array based on index. diff --git a/arrayfire/device.py b/arrayfire/device.py index 84594f2b3..53f302db5 100644 --- a/arrayfire/device.py +++ b/arrayfire/device.py @@ -150,6 +150,25 @@ def is_dbl_supported(device=None): 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): """ Block until all the functions on the device have completed execution. diff --git a/arrayfire/graphics.py b/arrayfire/graphics.py index 4e378aaf5..70881f42c 100644 --- a/arrayfire/graphics.py +++ b/arrayfire/graphics.py @@ -496,6 +496,34 @@ def set_axes_limits(self, xmin, xmax, ymin, ymax, zmin=None, zmax=None, exact=Fa 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 92c8d088b..2fdce0aef 100644 --- a/arrayfire/image.py +++ b/arrayfire/image.py @@ -711,6 +711,49 @@ def regions(image, conn = CONNECTIVITY.FOUR, out_type = Dtype.f32): conn.value, out_type.value)) return output +def confidenceCC(image, seedx, seedy, radius, multiplier, iters, segmented_value): + """ + Find the confidence connected components in the image. + + Parameters + ---------- + image : af.Array + - A 2 D arrayfire array representing an image. + Expects non-integral type + + seedx : af.Array + - An array with x-coordinates of seed points + + 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 + - Output array with resulting connected components + + """ + output = Array() + 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): """ Find the sobel derivatives of the image. diff --git a/arrayfire/lapack.py b/arrayfire/lapack.py index e6ffd5cb7..97ad92c7a 100644 --- a/arrayfire/lapack.py +++ b/arrayfire/lapack.py @@ -264,6 +264,39 @@ def inverse(A, options=MATPROP.NONE): 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): """ Rank of a matrix. diff --git a/arrayfire/library.py b/arrayfire/library.py index 0bdc2eeb9..915ca950a 100644 --- a/arrayfire/library.py +++ b/arrayfire/library.py @@ -97,6 +97,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) @@ -123,6 +124,7 @@ class Dtype(_Enum): u64 = _Enum_Type(9) s16 = _Enum_Type(10) u16 = _Enum_Type(11) + f16 = _Enum_Type(12) class Source(_Enum): """ @@ -152,6 +154,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): """ @@ -175,6 +179,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 @@ -446,6 +459,14 @@ class TOPK(_Enum): MIN = _Enum_Type(1) MAX = _Enum_Type(2) +class VARIANCE(_Enum): + """ + Variance bias type + """ + DEFAULT = _Enum_Type(0) + SAMPLE = _Enum_Type(1) + POPULATION = _Enum_Type(2) + _VER_MAJOR_PLACEHOLDER = "__VER_MAJOR__" def _setup(): diff --git a/arrayfire/ml.py b/arrayfire/ml.py new file mode 100644 index 000000000..9140cc278 --- /dev/null +++ b/arrayfire/ml.py @@ -0,0 +1,74 @@ +####################################################### +# 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): + """ + 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 + - 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/signal.py b/arrayfire/signal.py index ca27d21c2..1fa6c424d 100644 --- a/arrayfire/signal.py +++ b/arrayfire/signal.py @@ -972,6 +972,56 @@ def convolve2(signal, kernel, conv_mode = CONV_MODE.DEFAULT, conv_domain = CONV_ 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 diff --git a/arrayfire/statistics.py b/arrayfire/statistics.py index e6b4effd0..f47f3a48d 100644 --- a/arrayfire/statistics.py +++ b/arrayfire/statistics.py @@ -108,6 +108,48 @@ def var(a, isbiased=False, weights=None, dim=None): 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 stdev(a, dim=None): """ Calculate standard deviation along a given dimension. diff --git a/arrayfire/util.py b/arrayfire/util.py index 709bd7811..44af6000d 100644 --- a/arrayfire/util.py +++ b/arrayfire/util.py @@ -105,7 +105,8 @@ def get_reversion(): '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', @@ -118,7 +119,8 @@ def get_reversion(): Dtype.s64.value : 'l', Dtype.u64.value : 'L', Dtype.c32.value : 'F', - Dtype.c64.value : 'D'} + Dtype.c64.value : 'D', + Dtype.f16.value : 'hf'} to_c_type = {Dtype.f32.value : c_float_t, Dtype.f64.value : c_double_t, @@ -131,7 +133,8 @@ def get_reversion(): 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.c64.value : c_double_t * 2, + Dtype.f16.value : c_ushort_t} to_typename = {Dtype.f32.value : 'float', Dtype.f64.value : 'double', @@ -144,4 +147,5 @@ def get_reversion(): 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/setup.cfg b/setup.cfg index 0e02078f1..e4f536a88 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = arrayfire -version = 3.6.20181017 +version = 3.7.20200213 description = Python bindings for ArrayFire licence = BSD long_description = file: README.md diff --git a/tests/simple/algorithm.py b/tests/simple/algorithm.py index 5b40d6916..b9e42f138 100644 --- a/tests/simple/algorithm.py +++ b/tests/simple/algorithm.py @@ -27,6 +27,30 @@ def simple_algorithm(verbose=False): 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)) diff --git a/tests/simple/arith.py b/tests/simple/arith.py index 306b93fff..5d4d83d00 100644 --- a/tests/simple/arith.py +++ b/tests/simple/arith.py @@ -192,6 +192,7 @@ 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) diff --git a/tests/simple/data.py b/tests/simple/data.py index d80f9e125..d091497eb 100644 --- a/tests/simple/data.py +++ b/tests/simple/data.py @@ -72,5 +72,6 @@ def simple_data(verbose=False): af.replace(a, a > 0.3, -0.3) display_func(a) + display_func(af.pad(a, (1, 1, 0, 0), (2, 2, 0, 0))) _util.tests["data"] = simple_data diff --git a/tests/simple/image.py b/tests/simple/image.py index 1489e94dc..8c2212974 100644 --- a/tests/simple/image.py +++ b/tests/simple/image.py @@ -51,6 +51,9 @@ 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) display_func(dx) diff --git a/tests/simple/lapack.py b/tests/simple/lapack.py index e27fb6bc0..8cd3e9ac3 100644 --- a/tests/simple/lapack.py +++ b/tests/simple/lapack.py @@ -58,6 +58,9 @@ def simple_lapack(verbose=False): 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) diff --git a/tests/simple/signal.py b/tests/simple/signal.py index d92526488..9e72e6e35 100644 --- a/tests/simple/signal.py +++ b/tests/simple/signal.py @@ -101,6 +101,11 @@ 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) + g = af.convolve2NN(a, b, c, gradType=af.CONV_GRADIENT.DATA) + display_func(g) + a = af.randu(5, 5, 3) b = af.randu(3, 3, 2) display_func(af.convolve3(a, b)) diff --git a/tests/simple/statistics.py b/tests/simple/statistics.py index 2815af335..be639ea4a 100644 --- a/tests/simple/statistics.py +++ b/tests/simple/statistics.py @@ -34,6 +34,13 @@ def simple_statistics(verbose=False): print_func(af.var(a, isbiased=True)) print_func(af.var(a, weights=w)) + mean, var = af.mean_var(a, dim=0) + display_func(mean) + display_func(var) + mean, var = af.mean_var(a, weights=w, bias=VARIANCE.SAMPLE, dim=0) + display_func(mean) + display_func(var) + display_func(af.stdev(a, dim=0)) print_func(af.stdev(a)) From 76d8a5ba5242c0ee3375cee66e0f1a6b55d68586 Mon Sep 17 00:00:00 2001 From: syurkevi Date: Mon, 13 Apr 2020 17:41:56 -0400 Subject: [PATCH 07/38] update arrayfire library directory on 64 bit systems --- arrayfire/library.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arrayfire/library.py b/arrayfire/library.py index 915ca950a..6119293e0 100644 --- a/arrayfire/library.py +++ b/arrayfire/library.py @@ -551,7 +551,10 @@ class _clibrary(object): 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 - libname_full = self.AF_PATH + '/lib/' + libname + if os.path.isdir(self.AF_PATH + '/lib64'): + libname_full = self.AF_PATH + '/lib64/' + libname + else: + libname_full = self.AF_PATH + '/lib/' + libname return (libname, libname_full) def set_unsafe(self, name): From a1d5a1f57aedd8a97d3702a0a765b8dda9c1119e Mon Sep 17 00:00:00 2001 From: miketrumpis Date: Mon, 27 Jan 2020 10:49:35 -0800 Subject: [PATCH 08/38] BF: check AF library paths for pkg vs source install --- arrayfire/library.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arrayfire/library.py b/arrayfire/library.py index 6119293e0..e46fa2b08 100644 --- a/arrayfire/library.py +++ b/arrayfire/library.py @@ -517,7 +517,10 @@ def _setup(): 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/' From e7fdf7eb24746e60444b22659aeb3f5be6451710 Mon Sep 17 00:00:00 2001 From: syurkevi Date: Tue, 10 Mar 2020 14:00:49 -0400 Subject: [PATCH 09/38] adds missing deconv functions to python wrapper --- arrayfire/image.py | 72 +++++++++++++++++++++++++++++++++++++++++++ arrayfire/library.py | 15 +++++++++ tests/simple/image.py | 7 +++++ 3 files changed, 94 insertions(+) diff --git a/arrayfire/image.py b/arrayfire/image.py index 2fdce0aef..f626a4802 100644 --- a/arrayfire/image.py +++ b/arrayfire/image.py @@ -1324,6 +1324,78 @@ def anisotropic_diffusion(image, time_step, conductance, iterations, flux_functi 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. diff --git a/arrayfire/library.py b/arrayfire/library.py index e46fa2b08..863c6746f 100644 --- a/arrayfire/library.py +++ b/arrayfire/library.py @@ -459,6 +459,21 @@ class TOPK(_Enum): 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 diff --git a/tests/simple/image.py b/tests/simple/image.py index 8c2212974..6f2e12186 100644 --- a/tests/simple/image.py +++ b/tests/simple/image.py @@ -88,5 +88,12 @@ def simple_image(verbose=False): 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 From a2cd9cecdf1ae58a28ef6a52f92704683305aefc Mon Sep 17 00:00:00 2001 From: syurkevi Date: Wed, 29 Apr 2020 19:23:44 -0400 Subject: [PATCH 10/38] corrects invert operation to non-inplace bitwise inversion --- arrayfire/array.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arrayfire/array.py b/arrayfire/array.py index 289ef4699..50db2a9a8 100644 --- a/arrayfire/array.py +++ b/arrayfire/array.py @@ -1090,9 +1090,8 @@ def __invert__(self): Return ~self """ out = Array() - safe_call(backend.get().af_not(c_pointer(out.arr), self.arr)) - self = out - return self + safe_call(backend.get().af_bitnot(c_pointer(out.arr), self.arr)) + return out def __nonzero__(self): return self != 0 From e053bb5f35ae141db85e68755f0c522d13944cd3 Mon Sep 17 00:00:00 2001 From: syurkevi Date: Thu, 13 Aug 2020 19:39:23 -0400 Subject: [PATCH 11/38] adds updated approx functions to python wrapper --- arrayfire/signal.py | 204 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 185 insertions(+), 19 deletions(-) diff --git a/arrayfire/signal.py b/arrayfire/signal.py index 1fa6c424d..35e0fba87 100644 --- a/arrayfire/signal.py +++ b/arrayfire/signal.py @@ -27,7 +27,7 @@ def _scale_pos_axis1(y_curr, y_orig): 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): +def approx1(signal, x, method=INTERP.LINEAR, off_grid=0.0, xp = None, output = None): """ Interpolate along a single dimension.Interpolation is performed along axis 0 of the input array. @@ -51,6 +51,10 @@ def approx1(signal, x, method=INTERP.LINEAR, off_grid=0.0, xp = None): 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 ------- @@ -65,20 +69,86 @@ def approx1(signal, x, method=INTERP.LINEAR, off_grid=0.0, xp = None): where N is the length of the first dimension of `signal`. """ - output = Array() + 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))) - if(xp is not None): - pos0 = _scale_pos_axis0(x, xp) else: - pos0 = x + 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): + """ + 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. - safe_call(backend.get().af_approx1(c_pointer(output.arr), signal.arr, pos0.arr, - method.value, c_float_t(off_grid))) + 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, x, y, - method=INTERP.LINEAR, off_grid=0.0, xp = None, yp = None - ): + method=INTERP.LINEAR, off_grid=0.0, xp = None, yp = None, output = None): """ Interpolate along a two dimension.Interpolation is performed along axes 0 and 1 of the input array. @@ -112,6 +182,10 @@ def approx2(signal, x, y, 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 ------- @@ -127,22 +201,114 @@ def approx2(signal, x, y, and N is the length of the second dimension of `signal`. """ - output = Array() - - if(xp is not None): - pos0 = _scale_pos_axis0(x, xp) + 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: - pos0 = x + 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): + """ + 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: - if(yp is not None): - pos1 = _scale_pos_axis1(y, yp) + 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: - pos1 = y + 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))) - safe_call(backend.get().af_approx2(c_pointer(output.arr), signal.arr, - pos0.arr, pos1.arr, method.value, c_float_t(off_grid))) return output + def fft(signal, dim0 = None , scale = None): """ Fast Fourier Transform: 1D From a914b02e93a407394b318159df17f36362f79d28 Mon Sep 17 00:00:00 2001 From: syurkevi Date: Thu, 13 Aug 2020 19:40:10 -0400 Subject: [PATCH 12/38] adds gemm functionality, complex ctypes --- arrayfire/blas.py | 106 +++++++++++++++++++++++++++++++++++++++++++ arrayfire/library.py | 7 +++ 2 files changed, 113 insertions(+) diff --git a/arrayfire/blas.py b/arrayfire/blas.py index f0e9dfdc6..1f1cb1359 100644 --- a/arrayfire/blas.py +++ b/arrayfire/blas.py @@ -202,3 +202,109 @@ def dot(lhs, rhs, lhs_opts=MATPROP.NONE, rhs_opts=MATPROP.NONE, return_scalar = 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 = α ∗ opA(A) opB(B)+ β∗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/library.py b/arrayfire/library.py index 863c6746f..009a8e122 100644 --- a/arrayfire/library.py +++ b/arrayfire/library.py @@ -31,6 +31,13 @@ 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' From bd1b38e9b147e7d409957751a0f0324af23dd1c9 Mon Sep 17 00:00:00 2001 From: syurkevi Date: Fri, 24 Jul 2020 01:53:27 -0400 Subject: [PATCH 13/38] adds missing logical operations on Array --- arrayfire/array.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/arrayfire/array.py b/arrayfire/array.py index 50db2a9a8..1b71db2c7 100644 --- a/arrayfire/array.py +++ b/arrayfire/array.py @@ -1093,6 +1093,30 @@ def __invert__(self): 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 From f61d8103a6aaeddb30f796ce91504fb8d5f4a540 Mon Sep 17 00:00:00 2001 From: pradeep Date: Thu, 20 Aug 2020 14:29:59 +0530 Subject: [PATCH 14/38] Fix non-ASCII character(alpha,beta) usage in doc strings of gemm --- arrayfire/blas.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arrayfire/blas.py b/arrayfire/blas.py index 1f1cb1359..448261e90 100644 --- a/arrayfire/blas.py +++ b/arrayfire/blas.py @@ -209,10 +209,10 @@ def gemm(lhs, rhs, alpha=1.0, beta=0.0, lhs_opts=MATPROP.NONE, rhs_opts=MATPROP. This provides a general interface to the BLAS level 3 general matrix multiply (GEMM), which is generally defined as: - C = α ∗ opA(A) opB(B)+ β∗C + 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 + 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). From 5203d02e482844a9030325c09310dffe26611304 Mon Sep 17 00:00:00 2001 From: pradeep Date: Thu, 20 Aug 2020 14:30:59 +0530 Subject: [PATCH 15/38] Update copyright year and authors in package config and docs --- docs/conf.py | 4 ++-- setup.cfg | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 04937888d..44374afb1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -58,8 +58,8 @@ # General information about the project. project = 'ArrayFire' -copyright = '2016, Pavan Yalamanchili' -author = 'Pavan Yalamanchili' +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 diff --git a/setup.cfg b/setup.cfg index e4f536a88..831f2063b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,8 +4,8 @@ version = 3.7.20200213 description = Python bindings for ArrayFire licence = BSD long_description = file: README.md -maintainer = Pavan Yalamanchili -maintainer_email = contact@pavanky.com +maintainer = ArrayFire +maintainer_email = technical@arrayfire.com url = http://arrayfire.com classifiers = Programming Language :: Python From b4992b6f561d61bde74ca91246b637d78429c117 Mon Sep 17 00:00:00 2001 From: pradeep Date: Thu, 20 Aug 2020 14:33:51 +0530 Subject: [PATCH 16/38] Fix meanvar & convolve2NNGradient API unit tests --- tests/simple/signal.py | 4 +++- tests/simple/statistics.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/simple/signal.py b/tests/simple/signal.py index 9e72e6e35..0e3e8da9d 100644 --- a/tests/simple/signal.py +++ b/tests/simple/signal.py @@ -103,7 +103,9 @@ def simple_signal(verbose=False): c = af.convolve2NN(a, b) display_func(c) - g = af.convolve2NN(a, b, c, gradType=af.CONV_GRADIENT.DATA) + 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) diff --git a/tests/simple/statistics.py b/tests/simple/statistics.py index be639ea4a..174af0a5b 100644 --- a/tests/simple/statistics.py +++ b/tests/simple/statistics.py @@ -34,10 +34,10 @@ def simple_statistics(verbose=False): print_func(af.var(a, isbiased=True)) print_func(af.var(a, weights=w)) - mean, var = af.mean_var(a, dim=0) + mean, var = af.meanvar(a, dim=0) display_func(mean) display_func(var) - mean, var = af.mean_var(a, weights=w, bias=VARIANCE.SAMPLE, dim=0) + mean, var = af.meanvar(a, weights=w, bias=af.VARIANCE.SAMPLE, dim=0) display_func(mean) display_func(var) From a2a85ac8d8b17dddcf927cab3b0b0ff9567a579b Mon Sep 17 00:00:00 2001 From: pradeep Date: Thu, 20 Aug 2020 15:11:32 +0530 Subject: [PATCH 17/38] Fix documentation of convolve2GradientNN --- arrayfire/ml.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/arrayfire/ml.py b/arrayfire/ml.py index 9140cc278..7e0fc53de 100644 --- a/arrayfire/ml.py +++ b/arrayfire/ml.py @@ -18,11 +18,14 @@ def convolve2GradientNN(incoming_gradient, original_signal, original_kernel, convolved_output, stride = (1, 1), padding = (0, 0), dilation = (1, 1), gradType = CONV_GRADIENT.DEFAULT): """ - 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. + 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 @@ -33,12 +36,18 @@ def convolve2GradientNN(incoming_gradient, original_signal, original_kernel, con Parameters ----------- - signal: af.Array + 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. - kernel: af.Array + 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 From 96a66da4d8d3e09d51071b3eb4b52ab5e3f1bf86 Mon Sep 17 00:00:00 2001 From: pradeep Date: Thu, 20 Aug 2020 16:16:59 +0530 Subject: [PATCH 18/38] Fix a semantic error in indexing unit test --- tests/simple/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/simple/index.py b/tests/simple/index.py index a0b0cc757..8bb4b571a 100644 --- a/tests/simple/index.py +++ b/tests/simple/index.py @@ -70,7 +70,7 @@ def simple_index(verbose=False): rows = af.constant(0, 1, dtype=af.Dtype.s32) b = a[:, rows] display_func(b) - for r in rows: + for r in range(rows.elements()): display_func(r) display_func(b[:, r]) From 834dc14fcb132a8eb028159779671fc07ee7f958 Mon Sep 17 00:00:00 2001 From: pradeep Date: Thu, 20 Aug 2020 16:18:35 +0530 Subject: [PATCH 19/38] Add assets submodule for graphics examples to be added later --- .gitmodules | 3 +++ assets | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 assets 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/assets b/assets new file mode 160000 index 000000000..cd08d7496 --- /dev/null +++ b/assets @@ -0,0 +1 @@ +Subproject commit cd08d749611b324012555ad6f23fd76c5465bd6c From fabe335e30e902ffed537fc9636137507886efc1 Mon Sep 17 00:00:00 2001 From: Mark Poscablo Date: Mon, 4 Feb 2019 17:02:22 -0500 Subject: [PATCH 20/38] Added logistic regression example --- examples/common/idxio.py | 64 ++++++ .../machine_learning/logistic_regression.py | 205 ++++++++++++++++++ examples/machine_learning/mnist_common.py | 101 +++++++++ 3 files changed, 370 insertions(+) create mode 100644 examples/common/idxio.py create mode 100644 examples/machine_learning/logistic_regression.py create mode 100644 examples/machine_learning/mnist_common.py diff --git a/examples/common/idxio.py b/examples/common/idxio.py new file mode 100644 index 000000000..2aa4208a9 --- /dev/null +++ b/examples/common/idxio.py @@ -0,0 +1,64 @@ +#!/usr/bin/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/machine_learning/logistic_regression.py b/examples/machine_learning/logistic_regression.py new file mode 100644 index 000000000..79596cbc8 --- /dev/null +++ b/examples/machine_learning/logistic_regression.py @@ -0,0 +1,205 @@ +#!/usr/bin/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_proba(X, Weights): + Z = af.matmul(X, Weights) + return af.sigmoid(Z) + + +# Predict (log probability) based on given parameters +def predict_log_proba(X, Weights): + return af.log(predict_proba(X, Weights)) + + +# Give most likely class based on given parameters +def predict(X, Weights): + probs = predict_proba(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_proba(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(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_proba(train_feats, Weights) + test_outputs = predict_proba(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..3911cbd34 --- /dev/null +++ b/examples/machine_learning/mnist_common.py @@ -0,0 +1,101 @@ +#!/usr/bin/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 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): + idims, idata = read_idx('../../assets/examples/data/mnist/images-subset') + ldims, ldata = read_idx('../../assets/examples/data/mnist/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() From 956a2276c0299364de3bc2fb642ba341243b9e44 Mon Sep 17 00:00:00 2001 From: Mark Poscablo Date: Tue, 5 Feb 2019 10:54:24 -0500 Subject: [PATCH 21/38] Corrected predict method names --- .../machine_learning/logistic_regression.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/machine_learning/logistic_regression.py b/examples/machine_learning/logistic_regression.py index 79596cbc8..ffe7efa4b 100644 --- a/examples/machine_learning/logistic_regression.py +++ b/examples/machine_learning/logistic_regression.py @@ -34,19 +34,19 @@ def abserr(predicted, target): # Predict (probability) based on given parameters -def predict_proba(X, Weights): +def predict_prob(X, Weights): Z = af.matmul(X, Weights) return af.sigmoid(Z) # Predict (log probability) based on given parameters -def predict_log_proba(X, Weights): - return af.log(predict_proba(X, Weights)) +def predict_log_prob(X, Weights): + return af.log(predict_prob(X, Weights)) # Give most likely class based on given parameters -def predict(X, Weights): - probs = predict_proba(X, Weights) +def predict_class(X, Weights): + probs = predict_prob(X, Weights) _, classes = af.imax(probs, 1) return classes @@ -66,7 +66,7 @@ def cost(Weights, X, Y, lambda_param=1.0): lambdat[0, :] = 0 # Get the prediction - H = predict_proba(X, Weights) + H = predict_prob(X, Weights) # Cost of misprediction Jerr = -1 * af.sum(Y * af.log(H) + (1 - Y) * af.log(1 - H), dim=0) @@ -122,7 +122,7 @@ def benchmark_logistic_regression(train_feats, train_targets, test_feats): t0 = time.time() iters = 100 for i in range(iters): - test_outputs = predict(test_feats, Weights) + test_outputs = predict_prob(test_feats, Weights) af.eval(test_outputs) sync() t1 = time.time() @@ -172,8 +172,8 @@ def logit_demo(console, perc): af.sync() # Predict the results - train_outputs = predict_proba(train_feats, Weights) - test_outputs = predict_proba(test_feats, Weights) + 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))) From a2caffadcc20cbceaa51f58cbd81cc85830d8d6a Mon Sep 17 00:00:00 2001 From: pradeep Date: Tue, 8 Sep 2020 11:08:11 +0530 Subject: [PATCH 22/38] Use os util to fetch mnist data location from example location --- examples/machine_learning/mnist_common.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/machine_learning/mnist_common.py b/examples/machine_learning/mnist_common.py index 3911cbd34..6a5ba8be9 100644 --- a/examples/machine_learning/mnist_common.py +++ b/examples/machine_learning/mnist_common.py @@ -9,6 +9,7 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## +import os import sys sys.path.insert(0, '../common') from idxio import read_idx @@ -41,8 +42,10 @@ def classify(arr, k, expand_labels): def setup_mnist(frac, expand_labels): - idims, idata = read_idx('../../assets/examples/data/mnist/images-subset') - ldims, ldata = read_idx('../../assets/examples/data/mnist/labels-subset') + 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) From e85cefc745afb47511d58ce6d7de8bf905bb503b Mon Sep 17 00:00:00 2001 From: pradeep Date: Wed, 9 Sep 2020 13:11:03 +0530 Subject: [PATCH 23/38] Update python sheband in ML examples to widely adopted convention --- examples/common/idxio.py | 2 +- examples/machine_learning/logistic_regression.py | 2 +- examples/machine_learning/mnist_common.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/common/idxio.py b/examples/common/idxio.py index 2aa4208a9..8776c62dc 100644 --- a/examples/common/idxio.py +++ b/examples/common/idxio.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2019, ArrayFire diff --git a/examples/machine_learning/logistic_regression.py b/examples/machine_learning/logistic_regression.py index ffe7efa4b..b1d8b6e42 100644 --- a/examples/machine_learning/logistic_regression.py +++ b/examples/machine_learning/logistic_regression.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2019, ArrayFire diff --git a/examples/machine_learning/mnist_common.py b/examples/machine_learning/mnist_common.py index 6a5ba8be9..3f38302df 100644 --- a/examples/machine_learning/mnist_common.py +++ b/examples/machine_learning/mnist_common.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2019, ArrayFire From 0570570b67b9c23e778943fea0cb9ab26d7f00e5 Mon Sep 17 00:00:00 2001 From: syurkevi Date: Thu, 30 Aug 2018 16:43:57 -0400 Subject: [PATCH 24/38] adds computer vision example --- examples/computer_vision/fast.py | 81 ++++++++++++++++++ examples/computer_vision/harris.py | 119 +++++++++++++++++++++++++++ examples/computer_vision/matching.py | 105 +++++++++++++++++++++++ examples/computer_vision/susan.py | 81 ++++++++++++++++++ 4 files changed, 386 insertions(+) create mode 100644 examples/computer_vision/fast.py create mode 100644 examples/computer_vision/harris.py create mode 100644 examples/computer_vision/matching.py create mode 100644 examples/computer_vision/susan.py diff --git a/examples/computer_vision/fast.py b/examples/computer_vision/fast.py new file mode 100644 index 000000000..f70193e5e --- /dev/null +++ b/examples/computer_vision/fast.py @@ -0,0 +1,81 @@ +#!/usr/bin/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 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 fast_demo(console): + + if console: + img_color = af.load_image("../../assets/examples/images/square.png", True); + else: + img_color = af.load_image("../../assets/examples/images/man.jpg", True); + + img_color /= 255.0 + img = af.color_space(img_color, af.CSPACE.GRAY, af.CSPACE.RGB) + + features = af.fast(img) + + xs = features.get_xpos() + ys = features.get_ypos() + + 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..bb89c3ac4 --- /dev/null +++ b/examples/computer_vision/harris.py @@ -0,0 +1,119 @@ +#!/usr/bin/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 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): + + if console: + img_color = af.load_image("../../assets/examples/images/square.png", True); + else: + img_color = af.load_image("../../assets/examples/images/man.jpg", True); + + img_color /= 255.0 + img = af.color_space(img_color, af.CSPACE.GRAY, af.CSPACE.RGB) + + 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, "FAST Feature Detector") + + while not wnd.close(): + wnd.image(img_color) + else: + idx = af.where(corners) + + corners_x = idx / corners.dims()[0] + corners_y = idx % 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..797781212 --- /dev/null +++ b/examples/computer_vision/matching.py @@ -0,0 +1,105 @@ +#!/usr/bin/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 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): + + if console: + img_color = af.load_image("../../assets/examples/images/square.png", True); + else: + img_color = af.load_image("../../assets/examples/images/man.jpg", 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..6211128d5 --- /dev/null +++ b/examples/computer_vision/susan.py @@ -0,0 +1,81 @@ +#!/usr/bin/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 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): + + if console: + img_color = af.load_image("../../assets/examples/images/square.png", True); + else: + img_color = af.load_image("../../assets/examples/images/man.jpg", True); + + img_color /= 255.0 + img = af.color_space(img_color, af.CSPACE.GRAY, af.CSPACE.RGB) + + features = af.susan(img) + + xs = features.get_xpos() + ys = features.get_ypos() + + 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, "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) + From 7501eeab36d21beaf705b5546cdc1855fde98462 Mon Sep 17 00:00:00 2001 From: syurkevi Date: Thu, 30 Aug 2018 17:04:20 -0400 Subject: [PATCH 25/38] image range updates fixing matching --- examples/computer_vision/fast.py | 10 +++++----- examples/computer_vision/harris.py | 6 +++--- examples/computer_vision/susan.py | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/computer_vision/fast.py b/examples/computer_vision/fast.py index f70193e5e..584ff6618 100644 --- a/examples/computer_vision/fast.py +++ b/examples/computer_vision/fast.py @@ -25,8 +25,8 @@ 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) - ymin = max(0, y - draw_len) - ymax = min(img.dims()[0], y + draw_len) + 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 @@ -40,13 +40,13 @@ def fast_demo(console): else: img_color = af.load_image("../../assets/examples/images/man.jpg", True); - img_color /= 255.0 img = af.color_space(img_color, af.CSPACE.GRAY, af.CSPACE.RGB) + img_color /= 255.0 features = af.fast(img) - xs = features.get_xpos() - ys = features.get_ypos() + xs = features.get_xpos().to_list() + ys = features.get_ypos().to_list() draw_len = 3; num_features = features.num_features().value diff --git a/examples/computer_vision/harris.py b/examples/computer_vision/harris.py index bb89c3ac4..1c8e53804 100644 --- a/examples/computer_vision/harris.py +++ b/examples/computer_vision/harris.py @@ -40,8 +40,8 @@ def harris_demo(console): else: img_color = af.load_image("../../assets/examples/images/man.jpg", True); - img_color /= 255.0 img = af.color_space(img_color, af.CSPACE.GRAY, af.CSPACE.RGB) + img_color /= 255.0 ix, iy = af.gradient(img) ixx = ix * ix @@ -100,8 +100,8 @@ def harris_demo(console): else: idx = af.where(corners) - corners_x = idx / corners.dims()[0] - corners_y = idx % corners.dims()[0] + corners_x = idx / float(corners.dims()[0]) + corners_y = idx % float(corners.dims()[0]) print(corners_x) print(corners_y) diff --git a/examples/computer_vision/susan.py b/examples/computer_vision/susan.py index 6211128d5..025001b49 100644 --- a/examples/computer_vision/susan.py +++ b/examples/computer_vision/susan.py @@ -40,13 +40,13 @@ def susan_demo(console): else: img_color = af.load_image("../../assets/examples/images/man.jpg", True); - img_color /= 255.0 img = af.color_space(img_color, af.CSPACE.GRAY, af.CSPACE.RGB) + img_color /= 255.0 features = af.susan(img) - xs = features.get_xpos() - ys = features.get_ypos() + xs = features.get_xpos().to_list() + ys = features.get_ypos().to_list() draw_len = 3; num_features = features.num_features().value From 5416519319359abbab8f16d7f6a4b948d121ce03 Mon Sep 17 00:00:00 2001 From: pradeep Date: Tue, 8 Sep 2020 10:39:16 +0530 Subject: [PATCH 26/38] Use os path util to fetch example path to determine assets location --- examples/computer_vision/fast.py | 8 ++++++-- examples/computer_vision/harris.py | 8 ++++++-- examples/computer_vision/matching.py | 8 ++++++-- examples/computer_vision/susan.py | 8 ++++++-- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/examples/computer_vision/fast.py b/examples/computer_vision/fast.py index 584ff6618..1091271c8 100644 --- a/examples/computer_vision/fast.py +++ b/examples/computer_vision/fast.py @@ -11,6 +11,7 @@ from time import time import arrayfire as af +import os import sys def draw_corners(img, x, y, draw_len): @@ -35,10 +36,13 @@ def draw_corners(img, x, y, draw_len): def fast_demo(console): + root_path = os.path.dirname(os.path.abspath(__file__)) + file_path = root_path if console: - img_color = af.load_image("../../assets/examples/images/square.png", True); + file_path += "/../../assets/examples/images/square.png" else: - img_color = af.load_image("../../assets/examples/images/man.jpg", True); + 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 diff --git a/examples/computer_vision/harris.py b/examples/computer_vision/harris.py index 1c8e53804..34659ebc3 100644 --- a/examples/computer_vision/harris.py +++ b/examples/computer_vision/harris.py @@ -11,6 +11,7 @@ from time import time import arrayfire as af +import os import sys def draw_corners(img, x, y, draw_len): @@ -35,10 +36,13 @@ def draw_corners(img, x, y, draw_len): def harris_demo(console): + root_path = os.path.dirname(os.path.abspath(__file__)) + file_path = root_path if console: - img_color = af.load_image("../../assets/examples/images/square.png", True); + file_path += "/../../assets/examples/images/square.png" else: - img_color = af.load_image("../../assets/examples/images/man.jpg", True); + 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 diff --git a/examples/computer_vision/matching.py b/examples/computer_vision/matching.py index 797781212..8bdeb4b78 100644 --- a/examples/computer_vision/matching.py +++ b/examples/computer_vision/matching.py @@ -11,6 +11,7 @@ from time import time import arrayfire as af +import os import sys def normalize(a): @@ -45,10 +46,13 @@ def draw_rectangle(img, x, y, wx, wy): def templateMatchingDemo(console): + root_path = os.path.dirname(os.path.abspath(__file__)) + file_path = root_path if console: - img_color = af.load_image("../../assets/examples/images/square.png", True); + file_path += "/../../assets/examples/images/square.png" else: - img_color = af.load_image("../../assets/examples/images/man.jpg", True); + 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) diff --git a/examples/computer_vision/susan.py b/examples/computer_vision/susan.py index 025001b49..8fb00c3bd 100644 --- a/examples/computer_vision/susan.py +++ b/examples/computer_vision/susan.py @@ -11,6 +11,7 @@ from time import time import arrayfire as af +import os import sys def draw_corners(img, x, y, draw_len): @@ -35,10 +36,13 @@ def draw_corners(img, x, y, draw_len): def susan_demo(console): + root_path = os.path.dirname(os.path.abspath(__file__)) + file_path = root_path if console: - img_color = af.load_image("../../assets/examples/images/square.png", True); + file_path += "/../../assets/examples/images/square.png" else: - img_color = af.load_image("../../assets/examples/images/man.jpg", True); + 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 From 6fc8488fdca96959df8d51b3460e66bc348504b8 Mon Sep 17 00:00:00 2001 From: pradeep Date: Tue, 8 Sep 2020 10:39:41 +0530 Subject: [PATCH 27/38] Fix typo in harris detector example --- examples/computer_vision/harris.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/computer_vision/harris.py b/examples/computer_vision/harris.py index 34659ebc3..85388eea9 100644 --- a/examples/computer_vision/harris.py +++ b/examples/computer_vision/harris.py @@ -97,7 +97,7 @@ def harris_demo(console): print("Corners found: {}".format(good_corners)) if not console: # Previews color image with green crosshairs - wnd = af.Window(512, 512, "FAST Feature Detector") + wnd = af.Window(512, 512, "Harris Feature Detector") while not wnd.close(): wnd.image(img_color) From fc60534983fff230ad97755571e1425dbd1778d3 Mon Sep 17 00:00:00 2001 From: pradeep Date: Tue, 8 Sep 2020 10:39:47 +0530 Subject: [PATCH 28/38] Workaround in susan example for swapped coordinates of features I believe this has to be fixed from upstream and then later fixed here. Added a TODO inside the example for the same --- examples/computer_vision/susan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/computer_vision/susan.py b/examples/computer_vision/susan.py index 8fb00c3bd..4ae33fb09 100644 --- a/examples/computer_vision/susan.py +++ b/examples/computer_vision/susan.py @@ -59,7 +59,8 @@ def susan_demo(console): x = xs[f] y = ys[f] - img_color = draw_corners(img_color, x, y, draw_len) + # 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)) From 534b8c2ab4db5b08347f4d3d2f86a58ba8fcfdb6 Mon Sep 17 00:00:00 2001 From: pradeep Date: Wed, 9 Sep 2020 13:09:47 +0530 Subject: [PATCH 29/38] Update python sheband in examples to widely adopted convention --- examples/computer_vision/fast.py | 2 +- examples/computer_vision/harris.py | 2 +- examples/computer_vision/matching.py | 2 +- examples/computer_vision/susan.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/computer_vision/fast.py b/examples/computer_vision/fast.py index 1091271c8..e3a1299e3 100644 --- a/examples/computer_vision/fast.py +++ b/examples/computer_vision/fast.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2018, ArrayFire diff --git a/examples/computer_vision/harris.py b/examples/computer_vision/harris.py index 85388eea9..27fd6c6f8 100644 --- a/examples/computer_vision/harris.py +++ b/examples/computer_vision/harris.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2018, ArrayFire diff --git a/examples/computer_vision/matching.py b/examples/computer_vision/matching.py index 8bdeb4b78..cab0cccdf 100644 --- a/examples/computer_vision/matching.py +++ b/examples/computer_vision/matching.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2018, ArrayFire diff --git a/examples/computer_vision/susan.py b/examples/computer_vision/susan.py index 4ae33fb09..37a5dbd11 100644 --- a/examples/computer_vision/susan.py +++ b/examples/computer_vision/susan.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2018, ArrayFire From 65040c10833506f212f13e5bcc0e49cb20645e6e Mon Sep 17 00:00:00 2001 From: parag-hub <72143455+parag-hub@users.noreply.github.com> Date: Wed, 30 Sep 2020 23:52:27 +0530 Subject: [PATCH 30/38] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2d3f8c05a..fb3a1b8a1 100644 --- a/README.md +++ b/README.md @@ -51,14 +51,14 @@ pip install arrayfire pip install git+git://github.com/arrayfire/arrayfire-python.git@devel ``` -**Installing offline** +**Installing offline:** ``` cd path/to/arrayfire-python python setup.py install ``` -**Post Installation** +**Post Installation:** Please follow [these instructions](https://github.com/arrayfire/arrayfire-python/wiki) to ensure the arrayfire-python can find the arrayfire libraries. From 70c96cadc0b8b16f25f700f8936739b2382bc37b Mon Sep 17 00:00:00 2001 From: syurkevi Date: Tue, 2 Mar 2021 23:56:15 -0500 Subject: [PATCH 31/38] change setup.py to optionally build binaries in source distribution (#245) * adds initial skbuild configuration load from local lib directory loads local binaries if they are available * remove git from toml dependencies * additional cmake args in setup.py * add description format for pypi * directory corrections for skbuild * load local nvrtc-builtins when possible * make mkl libs static for distribution * Update README to include information about wheels * remove extra import platform Co-authored-by: pradeep --- CMakeLists.txt | 18 +++++++++ README.md | 28 ++++++------- arrayfire/library.py | 96 ++++++++++++++++++++++++++++++++++---------- pyproject.toml | 2 + setup.cfg | 5 ++- setup.py | 96 ++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 205 insertions(+), 40 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 pyproject.toml 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/README.md b/README.md index fb3a1b8a1..02af7fe26 100644 --- a/README.md +++ b/README.md @@ -25,30 +25,29 @@ def calc_pi_device(samples): 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. -## 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. +## 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. Wheels for some systems are available on PyPI. Unsupported systems will require separate installation of the ArrayFire C/C++ libraries and only the python wrapper will be installed in that case. +You can get the ArrayFire C/C++ library 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:** +**Install the last stable version:** ``` pip install arrayfire ``` -**Install the development version:** +**Install a pre-built wheel for a specific CUDA toolkit version:** +``` +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 +``` + +**Install the development source distribution:** ``` -pip install git+git://github.com/arrayfire/arrayfire-python.git@devel +pip install git+git://github.com/arrayfire/arrayfire-python.git@master ``` **Installing offline:** @@ -57,10 +56,11 @@ pip install git+git://github.com/arrayfire/arrayfire-python.git@devel 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/) **Post Installation:** -Please follow [these instructions](https://github.com/arrayfire/arrayfire-python/wiki) to ensure the arrayfire-python can find the arrayfire libraries. +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. To run arrayfire tests, you can run the following command from command line. diff --git a/arrayfire/library.py b/arrayfire/library.py index 009a8e122..27ca4f4d9 100644 --- a/arrayfire/library.py +++ b/arrayfire/library.py @@ -15,6 +15,7 @@ import ctypes as ct import traceback import os +import sys c_float_t = ct.c_float c_double_t = ct.c_double @@ -492,8 +493,6 @@ class VARIANCE(_Enum): _VER_MAJOR_PLACEHOLDER = "__VER_MAJOR__" def _setup(): - import platform - platform_name = platform.system() try: @@ -527,7 +526,7 @@ def _setup(): ct.windll.kernel32.SetErrorMode(0x0001 | 0x0002) if AF_SEARCH_PATH is None: - AF_SEARCH_PATH="C:/Program Files/ArrayFire/v" + AF_VER_MAJOR +"/" + AF_SEARCH_PATH = "C:/Program Files/ArrayFire/v" + AF_VER_MAJOR +"/" if CUDA_PATH is not None: CUDA_FOUND = os.path.isdir(CUDA_PATH + '/bin') and os.path.isdir(CUDA_PATH + '/nvvm/bin/') @@ -554,7 +553,12 @@ def _setup(): post = '.so.' + _VER_MAJOR_PLACEHOLDER if AF_SEARCH_PATH is None: - AF_SEARCH_PATH='/opt/arrayfire-' + AF_VER_MAJOR + '/' + 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/' @@ -566,21 +570,46 @@ def _setup(): else: raise OSError(platform_name + ' not supported') - if AF_PATH is None: - os.environ['AF_PATH'] = AF_SEARCH_PATH - - return pre, post, AF_SEARCH_PATH, CUDA_FOUND + return pre, post, AF_PATH, AF_SEARCH_PATH, CUDA_FOUND class _clibrary(object): + 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 os.path.isdir(self.AF_PATH + '/lib64'): - libname_full = self.AF_PATH + '/lib64/' + libname + + 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: - libname_full = self.AF_PATH + '/lib/' + libname - return (libname, libname_full) + libpaths.insert(2, (path_search, libname)) + return libpaths def set_unsafe(self, name): lib = self.__clibs[name] @@ -592,13 +621,19 @@ def __init__(self): more_info_str = "Please look at https://github.com/arrayfire/arrayfire-python/wiki for more information." - pre, post, AF_PATH, CUDA_FOUND = _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, @@ -618,7 +653,7 @@ def __init__(self): 'opencl' : 4} # Try to pre-load forge library if it exists - libnames = self.__libname('forge', head='', ver_major=FORGE_VER_MAJOR) + libnames = reversed(self.__libname('forge', head='', ver_major=FORGE_VER_MAJOR)) try: VERBOSE_LOADS = os.environ['AF_VERBOSE_LOADS'] == '1' @@ -628,14 +663,15 @@ def __init__(self): for libname in libnames: try: - ct.cdll.LoadLibrary(libname) + full_libname = libname[0] + libname[1] + ct.cdll.LoadLibrary(full_libname) if VERBOSE_LOADS: - print('Loaded ' + libname) + print('Loaded ' + full_libname) break except OSError: if VERBOSE_LOADS: traceback.print_exc() - print('Unable to load ' + libname) + print('Unable to load ' + full_libname) pass c_dim4 = c_dim_t*4 @@ -644,24 +680,39 @@ def __init__(self): # 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) + full_libname = libname[0] + libname[1] + + ct.cdll.LoadLibrary(full_libname) __name = 'unified' if name == '' else name - clib = ct.CDLL(libname) + 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 ' + libname) + 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 ' + libname) + print('Unable to load ' + full_libname) pass if (self.__name is None): @@ -689,6 +740,7 @@ def parse(self, res): lst.append(key) return tuple(lst) + backend = _clibrary() def set_backend(name, unsafe=False): 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/setup.cfg b/setup.cfg index 831f2063b..e5414158b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,9 +1,10 @@ [metadata] name = arrayfire -version = 3.7.20200213 +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 @@ -15,6 +16,8 @@ classifiers = [options] packages = find: +install_requires= + scikit-build [options.packages.find] include = arrayfire diff --git a/setup.py b/setup.py index e4d485c30..b6d37bb86 100644 --- a/setup.py +++ b/setup.py @@ -9,8 +9,98 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## -# TODO: Look for af libraries during setup +import os +import re -from setuptools import setup +# 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() -setup() From b37f7ea71f036d5f88045e0732c4e5012cacdeef Mon Sep 17 00:00:00 2001 From: syurkevi Date: Wed, 3 Mar 2021 01:06:28 -0500 Subject: [PATCH 32/38] Bump __af_version__ for docs --- __af_version__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__af_version__.py b/__af_version__.py index dfa78b826..16f06c5ae 100644 --- a/__af_version__.py +++ b/__af_version__.py @@ -9,6 +9,6 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## -version = "3.7" -release = "20200213" +version = "3.8" +release = "20210303" full_version = version + "." + release From 8ac4a8d317a4a2af5a61973bba6a0fb4e2977c08 Mon Sep 17 00:00:00 2001 From: pradeep Date: Wed, 3 Mar 2021 12:56:59 +0530 Subject: [PATCH 33/38] Add missing Machine Learning module documentation --- docs/arrayfire.ml.rst | 7 +++++++ docs/arrayfire.rst | 1 + 2 files changed, 8 insertions(+) create mode 100644 docs/arrayfire.ml.rst 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.rst b/docs/arrayfire.rst index 2951fcb3b..7504fd82b 100644 --- a/docs/arrayfire.rst +++ b/docs/arrayfire.rst @@ -26,6 +26,7 @@ Submodules arrayfire.interop arrayfire.lapack arrayfire.library + arrayfire.ml arrayfire.opencl arrayfire.random arrayfire.sparse From da192a7d307215f87185fc2ece436ed887405c06 Mon Sep 17 00:00:00 2001 From: pradeep Date: Wed, 3 Mar 2021 17:02:20 +0530 Subject: [PATCH 34/38] Refactor cov, var, stdev APIs to reflect 3.8 release Also adds set_cublas_mode utility function to explicitly enable tensor ops for blas functions --- arrayfire/algorithm.py | 28 ++++++++++++++++++++ arrayfire/cuda.py | 7 +++++ arrayfire/library.py | 7 +++++ arrayfire/statistics.py | 53 ++++++++++++++++++-------------------- tests/simple/statistics.py | 8 +++--- 5 files changed, 71 insertions(+), 32 deletions(-) diff --git a/arrayfire/algorithm.py b/arrayfire/algorithm.py index d5adbcce5..6f06cee14 100644 --- a/arrayfire/algorithm.py +++ b/arrayfire/algorithm.py @@ -250,6 +250,34 @@ 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. diff --git a/arrayfire/cuda.py b/arrayfire/cuda.py index ea24c0e30..e5ef819be 100644 --- a/arrayfire/cuda.py +++ b/arrayfire/cuda.py @@ -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/library.py b/arrayfire/library.py index 27ca4f4d9..1b3c8b3ea 100644 --- a/arrayfire/library.py +++ b/arrayfire/library.py @@ -490,6 +490,13 @@ class VARIANCE(_Enum): 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(): diff --git a/arrayfire/statistics.py b/arrayfire/statistics.py index f47f3a48d..158da18de 100644 --- a/arrayfire/statistics.py +++ b/arrayfire/statistics.py @@ -59,7 +59,7 @@ def mean(a, weights=None, dim=None): 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. @@ -68,9 +68,9 @@ def var(a, isbiased=False, weights=None, dim=None): a: af.Array The input array. - isbiased: optional: Boolean. default: False. - Boolean denoting population variance (false) or sample - variance (true). + 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 @@ -89,7 +89,7 @@ def var(a, isbiased=False, weights=None, dim=None): out = Array() if weights is None: - safe_call(backend.get().af_var(c_pointer(out.arr), a.arr, isbiased, c_int_t(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(c_pointer(out.arr), a.arr, weights.arr, c_int_t(dim))) @@ -99,7 +99,7 @@ def var(a, isbiased=False, weights=None, dim=None): imag = c_double_t(0) if weights is None: - safe_call(backend.get().af_var_all(c_pointer(real), c_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(c_pointer(real), c_pointer(imag), a.arr, weights.arr)) @@ -150,7 +150,7 @@ def meanvar(a, weights=None, bias=VARIANCE.DEFAULT, dim=-1): return mean_out, var_out -def stdev(a, dim=None): +def stdev(a, bias=VARIANCE.DEFAULT, dim=None): """ Calculate standard deviation along a given dimension. @@ -159,6 +159,10 @@ def stdev(a, dim=None): 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. @@ -171,48 +175,41 @@ def stdev(a, dim=None): """ if dim is not None: out = Array() - safe_call(backend.get().af_stdev(c_pointer(out.arr), a.arr, c_int_t(dim))) + safe_call(backend.get().af_stdev_v2(c_pointer(out.arr), a.arr, bias.value, + c_int_t(dim))) return out else: real = c_double_t(0) imag = c_double_t(0) - safe_call(backend.get().af_stdev_all(c_pointer(real), c_pointer(imag), a.arr)) + 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, isbiased=False, dim=None): +def cov(a, b, bias=VARIANCE.DEFAULT): """ Calculate covariance along a given dimension. Parameters ---------- a: af.Array - The input array. + Input array. - isbiased: optional: Boolean. default: False. - Boolean denoting whether biased estimate should be taken. + b: af.Array + Input array. - dim: optional: int. default: None. - The dimension for which to obtain the covariance from input data. + 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. + Array containing the covariance of the input array along a given dimension. """ - if dim is not None: - out = Array() - safe_call(backend.get().af_cov(c_pointer(out.arr), a.arr, isbiased, c_int_t(dim))) - return out - else: - real = c_double_t(0) - imag = c_double_t(0) - safe_call(backend.get().af_cov_all(c_pointer(real), c_pointer(imag), a.arr, isbiased)) - real = real.value - imag = imag.value - return real if imag == 0 else real + imag * 1j + 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): """ diff --git a/tests/simple/statistics.py b/tests/simple/statistics.py index 174af0a5b..39fe6703f 100644 --- a/tests/simple/statistics.py +++ b/tests/simple/statistics.py @@ -28,10 +28,10 @@ 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) @@ -45,9 +45,9 @@ def simple_statistics(verbose=False): 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)) From 96fa9768ee02e5fb5ffcaf3d1f744c898b141637 Mon Sep 17 00:00:00 2001 From: syurkevi Date: Thu, 4 Mar 2021 12:40:35 -0500 Subject: [PATCH 35/38] tweaks to pypi installation instructions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 02af7fe26..737399adb 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,14 @@ def calc_pi_device(samples): 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. ## 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. Wheels for some systems are available on PyPI. Unsupported systems will require separate installation of the ArrayFire C/C++ libraries and only the python wrapper will be installed in that case. +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: - [Download and install binaries](https://arrayfire.com/download) - [Build and install from source](https://github.com/arrayfire/arrayfire) -**Install the last stable version:** +**Install the last stable version of python wrapper:** ``` pip install arrayfire ``` From 069251051f3426b8e56da249d0d2b5e629769777 Mon Sep 17 00:00:00 2001 From: Tucker Yazdani Date: Fri, 22 Apr 2022 14:29:24 -0400 Subject: [PATCH 36/38] adds linear algebra examples --- examples/lin_algebra/cholesky.py | 36 ++++++++++++++++++++++++++++++++ examples/lin_algebra/lu.py | 30 ++++++++++++++++++++++++++ examples/lin_algebra/qr.py | 32 ++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 examples/lin_algebra/cholesky.py create mode 100644 examples/lin_algebra/lu.py create mode 100644 examples/lin_algebra/qr.py diff --git a/examples/lin_algebra/cholesky.py b/examples/lin_algebra/cholesky.py new file mode 100644 index 000000000..ade2b7f09 --- /dev/null +++ b/examples/lin_algebra/cholesky.py @@ -0,0 +1,36 @@ +import arrayfire as af + +def main(): + try: + device = 0 + af.info() + + n = 5 + t = af.randu(n,n) + inn = af.matmulNT(t,t) + af.identity(n,n)*n + + print("Running Cholesky InPlace\n") + cin_upper = inn + cin_lower = inn + + af.cholesky_inplace(cin_upper, True) + af.cholesky_inplace(cin_lower, False) + + print(cin_upper) + print(cin_lower) + + print("Running Cholesky Out of place\n") + + out_upper = af.cholesky(inn, True) + out_lower = af.cholesky(inn, False) + + # Do we want to print the array like above? If yes this is correct. + print(out_upper[0]) + print(out_lower[0]) + + + 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..9e3c083a7 --- /dev/null +++ b/examples/lin_algebra/lu.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +import arrayfire as af +def main(): + try: + device = 0 + #af.setDevice(device) + af.info() + + inn = af.randu(5,8) + print(inn) + + lin = inn + + print("Running LU InPlace\n") + # Ask if this is correct. + pivot = af.lu_inplace(lin) + print(lin) + print(pivot) + + print("Running LU with Upper Lower Factorization\n") + lower, upper, pivot = af.lu(inn) + 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..32f026994 --- /dev/null +++ b/examples/lin_algebra/qr.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +import arrayfire as af +def main(): + try: + #Skip device=argc.... + device = 0 + af.info() + + print("Running QR InPlace\n") + inn = af.randu(5,8) + print(inn) + + qin = inn + tau = af.qr_inplace(qin) + + print(qin) + print(tau) + + print("Running QR with Q and R factorization\n") + q,r,tau = af.qr(inn) + + print(q) + print(r) + print(tau) + + except Exception as e: + print("Error: ",str(e)) + + + +if __name__ == '__main__': + main() From 33b05582c55bcfcf1af127a3817abb239c9c118c Mon Sep 17 00:00:00 2001 From: syurkevi Date: Fri, 22 Apr 2022 15:17:41 -0400 Subject: [PATCH 37/38] minor fixes to linear algebra examples --- examples/lin_algebra/cholesky.py | 39 ++++++++++++++++++++------------ examples/lin_algebra/lu.py | 25 ++++++++++++-------- examples/lin_algebra/qr.py | 28 ++++++++++++++--------- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/examples/lin_algebra/cholesky.py b/examples/lin_algebra/cholesky.py index ade2b7f09..b3e550139 100644 --- a/examples/lin_algebra/cholesky.py +++ b/examples/lin_algebra/cholesky.py @@ -1,36 +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: - device = 0 af.info() - + n = 5 - t = af.randu(n,n) - inn = af.matmulNT(t,t) + af.identity(n,n)*n + t = af.randu(n, n) + arr_in = af.matmulNT(t, t) + af.identity(n, n) * n print("Running Cholesky InPlace\n") - cin_upper = inn - cin_lower = inn - + 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("Running Cholesky Out of place\n") + print("\nRunning Cholesky Out of place\n") - out_upper = af.cholesky(inn, True) - out_lower = af.cholesky(inn, False) - - # Do we want to print the array like above? If yes this is correct. - print(out_upper[0]) - print(out_lower[0]) + 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)) + print('Error: ', str(e)) if __name__ == '__main__': main() diff --git a/examples/lin_algebra/lu.py b/examples/lin_algebra/lu.py index 9e3c083a7..14405cc29 100644 --- a/examples/lin_algebra/lu.py +++ b/examples/lin_algebra/lu.py @@ -1,27 +1,32 @@ #!/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: - device = 0 - #af.setDevice(device) af.info() - inn = af.randu(5,8) - print(inn) - - lin = inn + in_array = af.randu(5,8) print("Running LU InPlace\n") - # Ask if this is correct. - pivot = af.lu_inplace(lin) - print(lin) + pivot = af.lu_inplace(in_array) + print(in_array) print(pivot) print("Running LU with Upper Lower Factorization\n") - lower, upper, pivot = af.lu(inn) + lower, upper, pivot = af.lu(in_array) print(lower) print(upper) print(pivot) + except Exception as e: print('Error: ', str(e)) diff --git a/examples/lin_algebra/qr.py b/examples/lin_algebra/qr.py index 32f026994..adeebfd0e 100644 --- a/examples/lin_algebra/qr.py +++ b/examples/lin_algebra/qr.py @@ -1,32 +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: - #Skip device=argc.... - device = 0 af.info() + in_array = af.randu(5,8) print("Running QR InPlace\n") - inn = af.randu(5,8) - print(inn) + q_in = in_array.copy() + print(q_in) - qin = inn - tau = af.qr_inplace(qin) + tau = af.qr_inplace(q_in) - print(qin) + print(q_in) print(tau) print("Running QR with Q and R factorization\n") - q,r,tau = af.qr(inn) + q, r, tau = af.qr(in_array) print(q) print(r) print(tau) except Exception as e: - print("Error: ",str(e)) - - + print("Error: ", str(e)) if __name__ == '__main__': main() From 94ebafa4dd8834ae1c62637b04ce44fa85c21bad Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 26 Apr 2023 02:44:22 +0300 Subject: [PATCH 38/38] [Data API] Array Object Implementation (#261) * Refactoring of basic functionality to create an empty Array * Replace dim4 with CShape * Add tests for array api. Minor fixes. Update CI * Add arithmetic operators w/o tests * Fix array init bug. Add __getitem__. Change pytest for active debug mode * Add reflected arithmetic and array operators * Place TODO for repr * Add bitwise operators. Add in-place operators. Add missing reflected operators * Add tests for arithmetic operators * Added to_list and to_ctypes_array * Fix bug when scalar is empty returns None * Fix typing in array object. Add tests * Change tests and found bug with reflected operators * Fix reflected operators bug. Add test coverage for the rest of the arithmetic operators * Add required by specification methods * Change utils. Add docstrings * Add docstrings for operators * Add docstrings for other operators. Remove docstrings from mocks * Change tags and typings * Change typings from python 3.10 to python 3.8 * Add readme with reference to run tests * Revert changes accidentally made in original array * Add constructor initialization warning. Add Note on deviation from spec. Dump minimal numpy version required. * Fix warning messages for non-standard functions * Add NOTE tag to functions that are not a part of spec but custom solutions --------- Co-authored-by: Anton --- .github/workflows/build.yaml | 29 + .gitignore | 9 +- arrayfire/array_api/README.md | 9 + arrayfire/array_api/__init__.py | 9 + arrayfire/array_api/array_object.py | 1105 +++++++++++++++++ arrayfire/array_api/config.py | 6 + arrayfire/array_api/device.py | 10 + arrayfire/array_api/dtype_functions.py | 30 + arrayfire/array_api/dtypes.py | 57 + arrayfire/array_api/pytest.ini | 4 + arrayfire/array_api/tests/__init__.py | 0 .../array_api/tests/test_array_object.py | 459 +++++++ arrayfire/array_api/utils.py | 11 + arrayfire/library.py | 5 - requirements.txt | 5 + setup.cfg | 34 +- 16 files changed, 1773 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/build.yaml create mode 100644 arrayfire/array_api/README.md create mode 100644 arrayfire/array_api/__init__.py create mode 100644 arrayfire/array_api/array_object.py create mode 100644 arrayfire/array_api/config.py create mode 100644 arrayfire/array_api/device.py create mode 100644 arrayfire/array_api/dtype_functions.py create mode 100644 arrayfire/array_api/dtypes.py create mode 100644 arrayfire/array_api/pytest.ini create mode 100644 arrayfire/array_api/tests/__init__.py create mode 100644 arrayfire/array_api/tests/test_array_object.py create mode 100644 arrayfire/array_api/utils.py create mode 100644 requirements.txt 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 aa7bb5f1d..047939aae 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ htmlcov/ nosetests.xml coverage.xml *,cover +.pytest_cache # Translations *.mo @@ -56,6 +57,8 @@ docs/_build/ # PyBuilder target/ -# IDE -.idea -.vscode +# mypy +.mypy_cache + +# Virtual environment +venv diff --git a/arrayfire/array_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/library.py b/arrayfire/library.py index 1b3c8b3ea..df68f97d8 100644 --- a/arrayfire/library.py +++ b/arrayfire/library.py @@ -506,7 +506,6 @@ def _setup(): AF_PATH = os.environ['AF_PATH'] except KeyError: AF_PATH = None - pass AF_SEARCH_PATH = AF_PATH @@ -514,7 +513,6 @@ def _setup(): CUDA_PATH = os.environ['CUDA_PATH'] except KeyError: CUDA_PATH= None - pass CUDA_FOUND = False @@ -666,7 +664,6 @@ def __init__(self): VERBOSE_LOADS = os.environ['AF_VERBOSE_LOADS'] == '1' except KeyError: VERBOSE_LOADS = False - pass for libname in libnames: try: @@ -679,7 +676,6 @@ def __init__(self): if VERBOSE_LOADS: traceback.print_exc() print('Unable to load ' + full_libname) - pass c_dim4 = c_dim_t*4 out = c_void_ptr_t(0) @@ -720,7 +716,6 @@ def __init__(self): if VERBOSE_LOADS: traceback.print_exc() print('Unable to load ' + full_libname) - pass if (self.__name is None): raise RuntimeError("Could not load any ArrayFire libraries.\n" + more_info_str) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..3b997e873 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# Build requirements +wheel~=0.38.4 + +# Development requirements +-e .[dev,test] diff --git a/setup.cfg b/setup.cfg index e5414158b..b40ac55e5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,16 +16,48 @@ classifiers = [options] packages = find: -install_requires= +install_requires = scikit-build +python_requires = + >=3.8.0 [options.packages.find] include = arrayfire exclude = examples tests +install_requires = + numpy~=1.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