From 3231f79955ee435a9ade2562b086fcaba7c8ede3 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 17 Apr 2024 01:04:58 +0300 Subject: [PATCH 1/3] Add overloads. Fix examples. Add missing reqs --- Makefile | 4 + arrayfire/__init__.py | 10 ++ arrayfire/array_api/_sorting_functions.py | 2 +- arrayfire/array_object.py | 28 ++-- arrayfire/library/device.py | 22 +++- arrayfire/library/linear_algebra.py | 41 +++++- arrayfire/library/signal_processing.py | 5 + arrayfire/library/statistics.py | 57 +++++++- arrayfire/library/vector_algorithms.py | 136 +++++++++++++++++++- examples/benchmarks/bench_blas.py | 17 ++- examples/benchmarks/bench_fft.py | 17 ++- examples/benchmarks/monte_carlo_pi.py | 39 ++++-- examples/financial/black_scholes_options.py | 99 +++++++------- examples/financial/heston_model.py | 103 +++++++-------- examples/financial/monte_carlo_options.py | 62 ++++++--- examples/getting_started/convolve.py | 61 ++++++--- examples/getting_started/intro.py | 70 ++++++---- examples/lin_algebra/cholesky.py | 47 ------- examples/lin_algebra/lu.py | 36 ------ examples/lin_algebra/qr.py | 40 ------ examples/linear_algebra/cholesky.py | 63 +++++++++ examples/linear_algebra/lu.py | 47 +++++++ examples/linear_algebra/qr.py | 46 +++++++ pyproject.toml | 3 + 24 files changed, 709 insertions(+), 346 deletions(-) delete mode 100644 examples/lin_algebra/cholesky.py delete mode 100644 examples/lin_algebra/lu.py delete mode 100644 examples/lin_algebra/qr.py create mode 100644 examples/linear_algebra/cholesky.py create mode 100644 examples/linear_algebra/lu.py create mode 100644 examples/linear_algebra/qr.py diff --git a/Makefile b/Makefile index 382dbc1..5ca9372 100755 --- a/Makefile +++ b/Makefile @@ -14,6 +14,10 @@ endif version : @python -c 'from arrayfire.version import VERSION; print(f"ArrayFire Python v{VERSION}")' +.PHONY : build +build : + @python -m build + # Dev .PHONY : pre-commit diff --git a/arrayfire/__init__.py b/arrayfire/__init__.py index 9794a7b..b48b9ca 100755 --- a/arrayfire/__init__.py +++ b/arrayfire/__init__.py @@ -562,6 +562,11 @@ "approx1_uniform", "approx2", "approx2_uniform", + "convolve1", + "convolve2", + "convolve2_nn", + "convolve2_separable", + "convolve3", ] from arrayfire.library.signal_processing import ( @@ -569,6 +574,11 @@ approx1_uniform, approx2, approx2_uniform, + convolve1, + convolve2, + convolve2_nn, + convolve2_separable, + convolve3, fft, fft2, fft2_c2r, diff --git a/arrayfire/array_api/_sorting_functions.py b/arrayfire/array_api/_sorting_functions.py index db67799..3956d19 100644 --- a/arrayfire/array_api/_sorting_functions.py +++ b/arrayfire/array_api/_sorting_functions.py @@ -35,7 +35,7 @@ def argsort(x: Array, /, *, axis: int = -1, descending: bool = False, stable: bo if axis == -1: axis = x.ndim - 1 - _, indices = af.sort(x._array, axis=axis, is_ascending=not descending, is_index_array=True) # type: ignore[misc] + _, indices = af.sort(x._array, axis=axis, is_ascending=not descending, is_index_array=True) return Array._new(indices) diff --git a/arrayfire/array_object.py b/arrayfire/array_object.py index 002b6fd..d44ee29 100755 --- a/arrayfire/array_object.py +++ b/arrayfire/array_object.py @@ -549,41 +549,41 @@ def __ne__(self, other: int | float | bool | Array, /) -> Array: # type: ignore # Reflected Arithmetic Operators - def __radd__(self, other: Array, /) -> Array: + def __radd__(self, other: int | float | Array, /) -> Array: """ Return other + self. """ return process_c_function(other, self, wrapper.add) - def __rsub__(self, other: Array, /) -> Array: + def __rsub__(self, other: int | float | Array, /) -> Array: """ Return other - self. """ return process_c_function(other, self, wrapper.sub) - def __rmul__(self, other: Array, /) -> Array: + def __rmul__(self, other: int | float | Array, /) -> Array: """ Return other * self. """ return process_c_function(other, self, wrapper.mul) - def __rtruediv__(self, other: Array, /) -> Array: + def __rtruediv__(self, other: int | float | Array, /) -> Array: """ Return other / self. """ return process_c_function(other, self, wrapper.div) - def __rfloordiv__(self, other: Array, /) -> Array: + def __rfloordiv__(self, other: int | float | Array, /) -> Array: # TODO return NotImplemented - def __rmod__(self, other: Array, /) -> Array: + def __rmod__(self, other: int | float | Array, /) -> Array: """ Return other % self. """ return process_c_function(other, self, wrapper.mod) - def __rpow__(self, other: Array, /) -> Array: + def __rpow__(self, other: int | float | Array, /) -> Array: """ Return other ** self. """ @@ -597,31 +597,31 @@ def __rmatmul__(self, other: Array, /) -> Array: # Reflected Bitwise Operators - def __rand__(self, other: Array, /) -> Array: + def __rand__(self, other: int | bool | Array, /) -> Array: """ Return other & self. """ return process_c_function(other, self, wrapper.bitand) - def __ror__(self, other: Array, /) -> Array: + def __ror__(self, other: int | bool | Array, /) -> Array: """ Return other | self. """ return process_c_function(other, self, wrapper.bitor) - def __rxor__(self, other: Array, /) -> Array: + def __rxor__(self, other: int | bool | Array, /) -> Array: """ Return other ^ self. """ return process_c_function(other, self, wrapper.bitxor) - def __rlshift__(self, other: Array, /) -> Array: + def __rlshift__(self, other: int | Array, /) -> Array: """ Return other << self. """ return process_c_function(other, self, wrapper.bitshiftl) - def __rrshift__(self, other: Array, /) -> Array: + def __rrshift__(self, other: int | Array, /) -> Array: """ Return other >> self. """ @@ -1126,11 +1126,11 @@ def process_c_function(lhs: int | float | Array, rhs: int | float | Array, c_fun lhs_array = lhs.arr rhs_array = rhs.arr - elif isinstance(lhs, Array) and isinstance(rhs, (int, float)): + elif isinstance(lhs, Array) and isinstance(rhs, int | float): lhs_array = lhs.arr rhs_array = wrapper.create_constant_array(rhs, lhs.shape, lhs.dtype) - elif isinstance(lhs, (int, float)) and isinstance(rhs, Array): + elif isinstance(lhs, int | float) and isinstance(rhs, Array): lhs_array = wrapper.create_constant_array(lhs, rhs.shape, rhs.dtype) rhs_array = rhs.arr diff --git a/arrayfire/library/device.py b/arrayfire/library/device.py index 7c29714..52f49c9 100644 --- a/arrayfire/library/device.py +++ b/arrayfire/library/device.py @@ -47,5 +47,25 @@ set_device, set_kernel_cache_directory, set_mem_step_size, - sync, ) +from arrayfire_wrapper.lib import sync as wrapper_sync + + +def sync(device_id: int | None = None) -> None: + """ + Blocks until all the functions on the specified device have completed execution. + + This function is used to synchronize the program execution with the operations + being carried out on a GPU or other computation device, ensuring that all + previously submitted operations are complete before the program proceeds. + + Parameters + ---------- + device_id : int | None, optional + The ID of the device on which to wait for all operations to complete. + If None is provided, the current active device is used. Default is None. + """ + if device_id is None: + device_id = get_device() + + wrapper_sync(device_id) diff --git a/arrayfire/library/linear_algebra.py b/arrayfire/library/linear_algebra.py index 543062e..ee1f09e 100644 --- a/arrayfire/library/linear_algebra.py +++ b/arrayfire/library/linear_algebra.py @@ -15,7 +15,7 @@ "solve", ] -from typing import cast +from typing import Literal, cast, overload import arrayfire_wrapper.lib as wrapper from arrayfire_wrapper.lib import is_lapack_available @@ -24,6 +24,17 @@ from arrayfire.array_object import afarray_as_array from arrayfire.library.constants import MatProp, Norm +# TODO +# Add missing documentation + + +@overload +def dot(lhs: Array, rhs: Array, /, *, return_scalar: Literal[True]) -> int | float | complex: ... + + +@overload +def dot(lhs: Array, rhs: Array, /, *, return_scalar: Literal[False] = False) -> Array: ... + def dot( lhs: Array, @@ -184,6 +195,14 @@ def matmul(lhs: Array, rhs: Array, /, lhs_opts: MatProp = MatProp.NONE, rhs_opts return cast(Array, wrapper.matmul(lhs.arr, rhs.arr, lhs_opts, rhs_opts)) +@overload +def cholesky(array: Array, /, is_upper: bool = True, *, inplace: Literal[True]) -> int: ... + + +@overload +def cholesky(array: Array, /, is_upper: bool = True, *, inplace: Literal[False] = False) -> tuple[Array, int]: ... + + def cholesky(array: Array, /, is_upper: bool = True, *, inplace: bool = False) -> int | tuple[Array, int]: if inplace: return wrapper.cholesky_inplace(array.arr, is_upper) @@ -192,6 +211,14 @@ def cholesky(array: Array, /, is_upper: bool = True, *, inplace: bool = False) - return Array.from_afarray(matrix), info +@overload +def lu(array: Array, /, *, inplace: Literal[True], is_lapack_pivot: bool = True) -> Array: ... + + +@overload +def lu(array: Array, /, *, inplace: Literal[False] = False, is_lapack_pivot: bool = True) -> tuple[Array, ...]: ... + + def lu(array: Array, /, *, inplace: bool = False, is_lapack_pivot: bool = True) -> Array | tuple[Array, ...]: if inplace: return Array.from_afarray(wrapper.lu_inplace(array.arr, is_lapack_pivot)) @@ -200,6 +227,14 @@ def lu(array: Array, /, *, inplace: bool = False, is_lapack_pivot: bool = True) return Array.from_afarray(lower), Array.from_afarray(upper), Array.from_afarray(pivot) +@overload +def qr(array: Array, /, *, inplace: Literal[True]) -> Array: ... + + +@overload +def qr(array: Array, /, *, inplace: Literal[False] = False) -> tuple[Array, ...]: ... + + def qr(array: Array, /, *, inplace: bool = False) -> Array | tuple[Array, ...]: if inplace: return Array.from_afarray(wrapper.qr_inplace(array.arr)) @@ -247,5 +282,5 @@ def solve(a: Array, b: Array, /, *, options: MatProp = MatProp.NONE, pivot: None return cast(Array, wrapper.solve(a.arr, b.arr, options)) -# TODO #good_first_issue -# Add Sparse functions +# TODO +# Create issues as #good_first_issue: add Sparse functions diff --git a/arrayfire/library/signal_processing.py b/arrayfire/library/signal_processing.py index 93ef03b..a392d39 100644 --- a/arrayfire/library/signal_processing.py +++ b/arrayfire/library/signal_processing.py @@ -21,6 +21,11 @@ "approx1_uniform", "approx2", "approx2_uniform", + "convolve1", + "convolve2", + "convolve2_nn", + "convolve2_separable", + "convolve3", ] from typing import cast diff --git a/arrayfire/library/statistics.py b/arrayfire/library/statistics.py index 7ad70c3..953ee4c 100644 --- a/arrayfire/library/statistics.py +++ b/arrayfire/library/statistics.py @@ -1,6 +1,6 @@ __all__ = ["corrcoef", "cov", "mean", "median", "stdev", "topk", "var"] -from typing import cast +from typing import cast, overload import arrayfire_wrapper.lib as wrapper @@ -8,6 +8,9 @@ from arrayfire.array_object import afarray_as_array from arrayfire.library.constants import TopK, VarianceBias +# TODO +# Add missing documentation + def corrcoef(x: Array, y: Array, /) -> int | float | complex: return wrapper.corrcoef(x.arr, y.arr) @@ -18,6 +21,22 @@ def cov(x: Array, y: Array, /, *, bias: VarianceBias = VarianceBias.DEFAULT) -> return cast(Array, wrapper.cov(x.arr, y.arr, bias)) +@overload +def mean(x: Array, /, axis: None = None, *, weights: None = None) -> int | float | complex: ... + + +@overload +def mean(x: Array, /, axis: int, *, weights: None = None) -> Array: ... + + +@overload +def mean(x: Array, /, axis: None, *, weights: Array) -> int | float | complex: ... + + +@overload +def mean(x: Array, /, axis: int, *, weights: Array) -> Array: ... + + def mean(x: Array, /, axis: None | int = None, *, weights: None | Array = None) -> int | float | complex | Array: if weights: if axis is None: @@ -31,6 +50,14 @@ def mean(x: Array, /, axis: None | int = None, *, weights: None | Array = None) return Array.from_afarray(wrapper.mean(x.arr, axis)) +@overload +def median(x: Array, /, axis: None = None) -> int | float | complex: ... + + +@overload +def median(x: Array, /, axis: int) -> Array: ... + + def median(x: Array, /, axis: None | int = None) -> int | float | complex | Array: if axis is None: return wrapper.median_all(x.arr) @@ -38,6 +65,14 @@ def median(x: Array, /, axis: None | int = None) -> int | float | complex | Arra return Array.from_afarray(wrapper.median(x.arr, axis)) +@overload +def stdev(x: Array, /, axis: None = None, *, bias: VarianceBias = VarianceBias.DEFAULT) -> int | float | complex: ... + + +@overload +def stdev(x: Array, /, axis: int, *, bias: VarianceBias = VarianceBias.DEFAULT) -> int | float | complex: ... + + def stdev( x: Array, /, axis: None | int = None, *, bias: VarianceBias = VarianceBias.DEFAULT ) -> int | float | complex | Array: @@ -52,6 +87,26 @@ def topk(x: Array, k: int, /, *, axis: int = 0, order: TopK = TopK.DEFAULT) -> t return Array.from_afarray(values), Array.from_afarray(indices) +@overload +def var( + x: Array, /, axis: None = None, *, weights: None = None, bias: VarianceBias = VarianceBias.DEFAULT +) -> int | float | complex: ... + + +@overload +def var(x: Array, /, axis: int, *, weights: None = None, bias: VarianceBias = VarianceBias.DEFAULT) -> Array: ... + + +@overload +def var( + x: Array, /, axis: None, *, weights: Array, bias: VarianceBias = VarianceBias.DEFAULT +) -> int | float | complex: ... + + +@overload +def var(x: Array, /, axis: int, *, weights: Array, bias: VarianceBias = VarianceBias.DEFAULT) -> Array: ... + + def var( x: Array, /, diff --git a/arrayfire/library/vector_algorithms.py b/arrayfire/library/vector_algorithms.py index a473604..cd66c06 100644 --- a/arrayfire/library/vector_algorithms.py +++ b/arrayfire/library/vector_algorithms.py @@ -20,7 +20,7 @@ "sort", ] -from typing import cast +from typing import Literal, cast, overload from arrayfire_wrapper import lib as wrapper @@ -116,6 +116,14 @@ def where(array: Array, /) -> Array: return cast(Array, wrapper.where(array.arr)) +@overload +def all_true(array: Array, axis: None = None) -> bool: ... + + +@overload +def all_true(array: Array, axis: int) -> Array: ... + + def all_true(array: Array, axis: int | None = None) -> bool | Array: """ Check if all the elements along a specified dimension are true. @@ -144,6 +152,14 @@ def all_true(array: Array, axis: int | None = None) -> bool | Array: return Array.from_afarray(wrapper.all_true(array.arr, axis)) +@overload +def any_true(array: Array, axis: None = None) -> bool: ... + + +@overload +def any_true(array: Array, axis: int) -> Array: ... + + def any_true(array: Array, axis: int | None = None) -> bool | Array: """ Check if any of the elements along a specified dimension are true. @@ -172,6 +188,22 @@ def any_true(array: Array, axis: int | None = None) -> bool | Array: return Array.from_afarray(wrapper.any_true(array.arr, axis)) +@overload +def sum(array: Array, /, *, axis: None = None, nan_value: None = None) -> int | float | complex: ... + + +@overload +def sum(array: Array, /, *, axis: None, nan_value: float) -> int | float | complex: ... + + +@overload +def sum(array: Array, /, *, axis: int, nan_value: None = None) -> Array: ... + + +@overload +def sum(array: Array, /, *, axis: int, nan_value: float) -> Array: ... + + def sum(array: Array, /, *, axis: int | None = None, nan_value: float | None = None) -> int | float | complex | Array: # FIXME documentation issues """ @@ -209,7 +241,23 @@ def sum(array: Array, /, *, axis: int | None = None, nan_value: float | None = N if nan_value is None: return Array.from_afarray(wrapper.sum(array.arr, axis)) - return Array.from_afarray(wrapper.sum_nan(array.arr, axis, nan_value)) # type: ignore[call-arg] + return Array.from_afarray(wrapper.sum_nan(array.arr, axis, nan_value)) + + +@overload +def product(array: Array, /, *, axis: None = None, nan_value: None = None) -> int | float | complex: ... + + +@overload +def product(array: Array, /, *, axis: None, nan_value: float) -> int | float | complex: ... + + +@overload +def product(array: Array, /, *, axis: int, nan_value: None = None) -> Array: ... + + +@overload +def product(array: Array, /, *, axis: int, nan_value: float) -> Array: ... def product( @@ -247,7 +295,23 @@ def product( if nan_value is None: return Array.from_afarray(wrapper.product(array.arr, axis)) - return Array.from_afarray(wrapper.product_nan(array.arr, axis, nan_value)) # type: ignore[call-arg] + return Array.from_afarray(wrapper.product_nan(array.arr, axis, nan_value)) + + +@overload +def count(array: Array, /, *, axis: None = None, keys: None = None) -> int | float | complex: ... + + +@overload +def count(array: Array, /, *, axis: int, keys: None = None) -> Array: ... + + +@overload +def count(array: Array, /, *, axis: None, keys: Array) -> tuple[Array, Array]: ... + + +@overload +def count(array: Array, /, *, axis: int, keys: Array) -> tuple[Array, Array]: ... def count( @@ -317,6 +381,14 @@ def count( return Array.from_afarray(wrapper.count(array.arr, axis)) +@overload +def imax(array: Array, /, *, axis: None = None) -> tuple[int | float | complex, int]: ... + + +@overload +def imax(array: Array, /, *, axis: int) -> tuple[Array, Array]: ... + + def imax(array: Array, /, *, axis: int | None = None) -> tuple[int | float | complex, int] | tuple[Array, Array]: """ Find the maximum value and its location within an ArrayFire array along a specified dimension, or globally if no @@ -380,6 +452,24 @@ def imax(array: Array, /, *, axis: int | None = None) -> tuple[int | float | com return Array.from_afarray(maximum), Array.from_afarray(location) +@overload +def max(array: Array, /, *, axis: int, keys: None = None, ragged_len: None = None) -> Array: ... + + +@overload +def max( + array: Array, /, *, axis: None = None, keys: None = None, ragged_len: None = None +) -> int | float | complex: ... + + +@overload +def max(array: Array, /, *, axis: int, keys: Array, ragged_len: None = None) -> tuple[Array, Array]: ... + + +@overload +def max(array: Array, /, *, axis: int, keys: None = None, ragged_len: Array) -> tuple[Array, Array]: ... + + def max( array: Array, /, *, axis: int | None = None, keys: Array | None = None, ragged_len: Array | None = None ) -> int | float | complex | Array | tuple[Array, Array]: @@ -443,6 +533,14 @@ def max( return Array.from_afarray(wrapper.max(array.arr, axis)) +@overload +def imin(array: Array, /, *, axis: None = None) -> tuple[int | float | complex, int]: ... + + +@overload +def imin(array: Array, /, *, axis: int) -> tuple[Array, Array]: ... + + def imin(array: Array, /, *, axis: int | None = None) -> tuple[int | float | complex, int] | tuple[Array, Array]: """ Find the value and location of the minimum value in an ArrayFire array along a specified dimension, or globally @@ -494,6 +592,14 @@ def imin(array: Array, /, *, axis: int | None = None) -> tuple[int | float | com return Array.from_afarray(minimum), Array.from_afarray(location) +@overload +def min(array: Array, /, *, axis: None = None) -> int | float | complex: ... + + +@overload +def min(array: Array, /, *, axis: int) -> Array: ... + + def min(array: Array, /, *, axis: int | None = None) -> int | float | complex | Array: """ Finds the minimum value in an ArrayFire array, optionally along a specified axis. @@ -827,6 +933,30 @@ def set_unique(array: Array, /, *, is_sorted: bool = False) -> Array: return cast(Array, wrapper.set_unique(array.arr, is_sorted)) +@overload +def sort( + array: Array, + /, + axis: int = 0, + is_ascending: bool = True, + *, + keys: None = None, + is_index_array: Literal[False] = False, +) -> Array: ... + + +@overload +def sort( + array: Array, /, axis: int = 0, is_ascending: bool = True, *, keys: Array, is_index_array: Literal[False] = False +) -> tuple[Array, Array]: ... + + +@overload +def sort( + array: Array, /, axis: int = 0, is_ascending: bool = True, *, keys: None = None, is_index_array: Literal[True] +) -> tuple[Array, Array]: ... + + def sort( array: Array, /, diff --git a/examples/benchmarks/bench_blas.py b/examples/benchmarks/bench_blas.py index 2a88171..91cf478 100644 --- a/examples/benchmarks/bench_blas.py +++ b/examples/benchmarks/bench_blas.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2024, ArrayFire @@ -9,23 +9,23 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## - import sys from time import time +from typing import Any, Callable import arrayfire as af try: import numpy as np except ImportError: - np = None # type: ignore[assignment] + raise ImportError("Please install arrayfire-python[benchmarks] or numpy directly to run this example.") -def calc_arrayfire(n): +def calc_arrayfire(n: int) -> Callable: A = af.randu((n, n)) af.sync(-1) - def run(iters): + def run(iters: int) -> None: for t in range(iters): B = af.matmul(A, A) # noqa: F841 af.sync(-1) @@ -33,18 +33,18 @@ def run(iters): return run -def calc_numpy(n): +def calc_numpy(n: int) -> Callable: np.random.seed(1) A = np.random.rand(n, n).astype(np.float32) - def run(iters): + def run(iters: int) -> None: for t in range(iters): B = np.dot(A, A) # noqa: F841 return run -def bench(calc, iters=100, upto=2048): +def bench(calc: Any, iters: int = 100, upto: int = 2048) -> None: _, name = calc.__name__.split("_") print("Benchmark N x N matrix multiply on %s" % name) @@ -58,7 +58,6 @@ def bench(calc, iters=100, upto=2048): if __name__ == "__main__": - if len(sys.argv) > 1: af.set_device(int(sys.argv[1])) diff --git a/examples/benchmarks/bench_fft.py b/examples/benchmarks/bench_fft.py index 70681be..826e85c 100644 --- a/examples/benchmarks/bench_fft.py +++ b/examples/benchmarks/bench_fft.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2024, ArrayFire @@ -9,23 +9,23 @@ # http://arrayfire.com/licenses/BSD-3-Clause ######################################################## - import sys from time import time +from typing import Any, Callable import arrayfire as af try: import numpy as np except ImportError: - np = None # type: ignore[assignment] + raise ImportError("Please install arrayfire-python[benchmarks] or numpy directly to run this example.") -def calc_arrayfire(n): +def calc_arrayfire(n: int) -> Callable: A = af.randu((n, n)) af.sync(-1) - def run(iters): + def run(iters: int) -> None: for t in range(iters): B = af.fft2(A) # noqa: F841 @@ -34,18 +34,18 @@ def run(iters): return run -def calc_numpy(n): +def calc_numpy(n: int) -> Callable: np.random.seed(1) A = np.random.rand(n, n).astype(np.float32) - def run(iters): + def run(iters: int) -> None: for t in range(iters): B = np.fft.fft2(A) # noqa: F841 return run -def bench(calc, iters=100, upto=13): +def bench(calc: Any, iters: int = 100, upto: int = 13) -> None: _, name = calc.__name__.split("_") print("Benchmark N x N 2D fft on %s" % name) @@ -60,7 +60,6 @@ def bench(calc, iters=100, upto=13): if __name__ == "__main__": - if len(sys.argv) > 1: af.set_device(int(sys.argv[1])) diff --git a/examples/benchmarks/monte_carlo_pi.py b/examples/benchmarks/monte_carlo_pi.py index 3986312..f7495ce 100644 --- a/examples/benchmarks/monte_carlo_pi.py +++ b/examples/benchmarks/monte_carlo_pi.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2024, ArrayFire @@ -12,8 +12,12 @@ import sys from random import random from time import time +from typing import Any, overload -import numpy as np +try: + import numpy as np +except ImportError: + raise ImportError("Please install arrayfire-python[benchmarks] or numpy directly to run this example.") import arrayfire as af @@ -23,30 +27,45 @@ frange = range # Python3 -# Having the function outside is faster than the lambda inside -def in_circle(x, y): +@overload +def in_circle(x: af.Array, y: af.Array) -> af.Array: return (x * x + y * y) < 1 -def calc_pi_device(samples): +@overload +def in_circle(x: np.ndarray, y: np.ndarray) -> np.ndarray: ... + + +@overload +def in_circle(x: float, y: float) -> bool: ... + + +# Having the function outside is faster than the lambda inside +def in_circle(x: af.Array | np.ndarray | float, y: af.Array | np.ndarray | float) -> af.Array | np.ndarray | float: + return (x * x + y * y) < 1 # type: ignore[operator] # NOTE no override for np.ndarray + + +def calc_pi_device(samples: int) -> af.Array: x = af.randu((samples,)) y = af.randu((samples,)) - return 4 * af.sum(in_circle(x, y)) / samples + res = in_circle(x, y) + return 4 * af.sum(res) / samples # type: ignore[return-value, operator] -def calc_pi_numpy(samples): +def calc_pi_numpy(samples: int) -> af.Array: np.random.seed(1) x = np.random.rand(samples).astype(np.float32) y = np.random.rand(samples).astype(np.float32) - return 4.0 * np.sum(in_circle(x, y)) / samples + res = in_circle(x, y) + return 4.0 * np.sum(res) / samples # type: ignore[no-any-return] -def calc_pi_host(samples): +def calc_pi_host(samples: int) -> float: count = sum(1 for k in frange(samples) if in_circle(random(), random())) return 4 * float(count) / samples -def bench(calc_pi, samples=1000000, iters=25): +def bench(calc_pi: Any, samples: int = 1000000, iters: int = 25) -> None: func_name = calc_pi.__name__[8:] print( "Monte carlo estimate of pi on %s with %d million samples: %f" % (func_name, samples / 1e6, calc_pi(samples)) diff --git a/examples/financial/black_scholes_options.py b/examples/financial/black_scholes_options.py index 7dbd413..23c0bac 100644 --- a/examples/financial/black_scholes_options.py +++ b/examples/financial/black_scholes_options.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2024, ArrayFire @@ -15,70 +15,67 @@ import arrayfire as af -sqrt2 = math.sqrt(2.0) - - -def cnd(x): - temp = x > 0 - return temp * (0.5 + af.erf(x / sqrt2) / 2) + (1 - temp) * (0.5 - af.erf((-x) / sqrt2) / 2) +def initialize_device() -> None: + """Initialize the ArrayFire device based on command line arguments.""" + device_id = int(sys.argv[1]) if len(sys.argv) > 1 else 0 + af.set_device(device_id) + af.info() -def black_scholes(S, X, R, V, T): - # S = Underlying stock price - # X = Strike Price - # R = Risk free rate of interest - # V = Volatility - # T = Time to maturity - d1 = af.log(S / X) - d1 = d1 + (R + (V * V) * 0.5) * T - d1 = d1 / (V * af.sqrt(T)) +def cumulative_normal_distribution(x: af.Array) -> af.Array: + """Calculate the cumulative normal distribution using ArrayFire.""" + sqrt2 = math.sqrt(2.0) + condition = x > 0 + lhs = condition * (0.5 + af.erf(x / sqrt2) / 2) + rhs = (1 - condition) * (0.5 - af.erf((-x) / sqrt2) / 2) + return lhs + rhs - d2 = d1 - (V * af.sqrt(T)) - cnd_d1 = cnd(d1) - cnd_d2 = cnd(d2) - C = S * cnd_d1 - (X * af.exp((-R) * T) * cnd_d2) - P = X * af.exp((-R) * T) * (1 - cnd_d2) - (S * (1 - cnd_d1)) +def black_scholes(S: af.Array, X: af.Array, R: af.Array, V: af.Array, T: af.Array) -> tuple[af.Array, af.Array]: + """Compute call and put options prices using the Black-Scholes formula.""" + d1 = (af.log(S / X) + (R + 0.5 * V**2) * T) / (V * af.sqrt(T)) + d2 = d1 - V * af.sqrt(T) - return (C, P) + cnd_d1 = cumulative_normal_distribution(d1) + cnd_d2 = cumulative_normal_distribution(d2) + C = S * cnd_d1 - X * af.exp(-R * T) * cnd_d2 + P = X * af.exp(-R * T) * (1 - cnd_d2) - S * (1 - cnd_d1) + return C, P -if __name__ == "__main__": - if len(sys.argv) > 1: - af.set_device(int(sys.argv[1])) - af.info() +def benchmark_black_scholes(num_elements: int, num_iter: int = 100) -> None: + """Benchmark the Black-Scholes model over varying matrix sizes.""" M = 4000 - - S = af.randu((M, 1)) - X = af.randu((M, 1)) - R = af.randu((M, 1)) - V = af.randu((M, 1)) - T = af.randu((M, 1)) - - (C, P) = black_scholes(S, X, R, V, T) - af.eval(C) - af.eval(P) - af.sync() - - num_iter = 100 for N in range(50, 501, 50): - S = af.randu((M, N)) - X = af.randu((M, N)) - R = af.randu((M, N)) - V = af.randu((M, N)) - T = af.randu((M, N)) - af.sync() + S, X, R, V, T = (af.randu((M, N)) for _ in range(5)) - print("Input data size: %d elements" % (M * N)) + print(f"Input data size: {M * N} elements") start = time() - for i in range(num_iter): - (C, P) = black_scholes(S, X, R, V, T) - af.eval(C) - af.eval(P) + for _ in range(num_iter): + C, P = black_scholes(S, X, R, V, T) + af.eval(C, P) af.sync() + sec = (time() - start) / num_iter + print(f"Mean GPU Time: {1000.0 * sec:.6f} ms\n") + + +def main() -> None: + initialize_device() - print("Mean GPU Time: %0.6f ms\n\n" % (1000.0 * sec)) + # Run a small test to ensure that everything is set up correctly. + M = 4000 + test_arrays = (af.randu((M, 1)) for _ in range(5)) + C, P = black_scholes(*test_arrays) + af.eval(C, P) + af.sync() + + # Benchmark Black-Scholes over varying sizes of input data. + benchmark_black_scholes(M) + + +if __name__ == "__main__": + main() diff --git a/examples/financial/heston_model.py b/examples/financial/heston_model.py index 3b346ac..443372d 100644 --- a/examples/financial/heston_model.py +++ b/examples/financial/heston_model.py @@ -1,49 +1,39 @@ -#!/usr/bin/python +#!/usr/bin/env python -############################################################################################## -# Copyright (c) 2015, Michael Nowotny +####################################################### +# Copyright (c) 2024, ArrayFire # All rights reserved. # -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation and/or other -# materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its contributors may be used -# to endorse or promote products derived from this software without specific -# prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -############################################################################################### +# 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 math import time +from typing import Tuple import arrayfire as af -def simulateHestonModel(T, N, R, mu, kappa, vBar, sigmaV, rho, x0, v0): - - deltaT = T / (float)(N - 1) +def initialize_parameters() -> Tuple[float, float, float, float, float, float, float, float]: + """Initialize and return model parameters.""" + r = math.log(1.0319) # risk-free rate + rho = -0.82 # instantaneous correlation between Brownian motions + sigmaV = 0.14 # variance of volatility + kappa = 3.46 # mean reversion speed + vBar = 0.008 # mean variance + k = math.log(0.95) # strike price, converted to log space + x0 = 0 # initial log stock price + v0 = 0.087**2 # initial volatility + return r, rho, sigmaV, kappa, vBar, k, x0, v0 - x = [af.constant(x0, (R,)), af.constant(0, (R,))] - v = [af.constant(v0, (R,)), af.constant(0, (R,))] +def simulate_heston_model( + T: int, N: int, R: int, mu: float, kappa: float, vBar: float, sigmaV: float, rho: float, x0: float, v0: float +) -> Tuple[af.Array, af.Array]: + """Simulate the Heston model for given parameters and return the resulting arrays.""" + deltaT = T / (N - 1) sqrtDeltaT = math.sqrt(deltaT) sqrtOneMinusRhoSquare = math.sqrt(1 - rho**2) @@ -52,50 +42,43 @@ def simulateHestonModel(T, N, R, mu, kappa, vBar, sigmaV, rho, x0, v0): m[1] = sqrtOneMinusRhoSquare zeroArray = af.constant(0, (R, 1)) + x = [af.constant(x0, (R,)) for _ in range(2)] + v = [af.constant(v0, (R,)) for _ in range(2)] + for t in range(1, N): - tPrevious = (t + 1) % 2 - tCurrent = t % 2 + t_previous = (t - 1) % 2 + t_current = t % 2 dBt = af.randn((R, 2)) * sqrtDeltaT - - vLag = af.maxof(v[tPrevious], zeroArray) + vLag = af.maxof(v[t_previous], zeroArray) sqrtVLag = af.sqrt(vLag) - x[tCurrent] = x[tPrevious] + (mu - 0.5 * vLag) * deltaT + sqrtVLag * dBt[:, 0] - v[tCurrent] = vLag + kappa * (vBar - vLag) * deltaT + sigmaV * (sqrtVLag * af.matmul(dBt, m)) + x[t_current] = x[t_previous] + (mu - 0.5 * vLag) * deltaT + sqrtVLag * dBt[:, 0] + v[t_current] = vLag + kappa * (vBar - vLag) * deltaT + sigmaV * sqrtVLag * af.matmul(dBt, m) - return (x[tCurrent], af.maxof(v[tCurrent], zeroArray)) + return x[t_current], af.maxof(v[t_current], zeroArray) -def main(): +def main() -> None: T = 1 nT = 20 * T R_first = 1000 R = 5000000 + r, rho, sigmaV, kappa, vBar, k, x0, v0 = initialize_parameters() - x0 = 0 # initial log stock price - v0 = 0.087**2 # initial volatility - r = math.log(1.0319) # risk-free rate - rho = -0.82 # instantaneous correlation between Brownian motions - sigmaV = 0.14 # variance of volatility - kappa = 3.46 # mean reversion speed - vBar = 0.008 # mean variance - k = math.log(0.95) # strike price - - # first run - (x, v) = simulateHestonModel(T, nT, R_first, r, kappa, vBar, sigmaV, rho, x0, v0) + # Initial simulation + simulate_heston_model(T, nT, R_first, r, kappa, vBar, sigmaV, rho, x0, v0) - # Price plain vanilla call option + # Time the pricing of a vanilla call option tic = time.time() - (x, v) = simulateHestonModel(T, nT, R, r, kappa, vBar, sigmaV, rho, x0, v0) + x, v = simulate_heston_model(T, nT, R, r, kappa, vBar, sigmaV, rho, x0, v0) af.sync() toc = time.time() - tic K = math.exp(k) - zeroConstant = af.constant(0, (R,)) - C_CPU = math.exp(-r * T) * af.mean(af.maxof(af.exp(x) - K, zeroConstant)) - print("Time elapsed = {} secs".format(toc)) - print("Call price = {}".format(C_CPU)) - print(af.mean(v)) + C_CPU = math.exp(-r * T) * af.mean(af.maxof(af.exp(x) - K, af.constant(0, (R,)))) + print(f"Time elapsed = {toc:.3f} secs") + print(f"Call price = {C_CPU:.6f}") + print(f"Average final variance = {af.mean(v):.6f}") if __name__ == "__main__": diff --git a/examples/financial/monte_carlo_options.py b/examples/financial/monte_carlo_options.py index 3792c89..eed480a 100644 --- a/examples/financial/monte_carlo_options.py +++ b/examples/financial/monte_carlo_options.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2024, ArrayFire @@ -12,55 +12,79 @@ import math import sys from time import time +from typing import cast import arrayfire as af -def monte_carlo_options(N, K, t, vol, r, strike, steps, use_barrier=True, B=None, ty=af.float32): - payoff = af.constant(0, (N, 1), dtype=ty) - - dt = t / float(steps - 1) +def monte_carlo_options( + N: int, + K: float, + t: float, + vol: float, + r: float, + strike: int, + steps: int, + use_barrier: bool = True, + B: float | None = None, + ty: af.Dtype = af.float32, +) -> float: + dt = t / (steps - 1) s = af.constant(strike, (N, 1), dtype=ty) randmat = af.randn((N, steps - 1), dtype=ty) - randmat = af.exp((r - (vol * vol * 0.5)) * dt + vol * math.sqrt(dt) * randmat) + randmat = af.exp((r - (vol**2 * 0.5)) * dt + vol * math.sqrt(dt) * randmat) S = af.product(af.join(1, s, randmat), axis=1) if use_barrier: + if B is None: + raise ValueError("Barrier value B must be provided if use_barrier is True.") S = S * af.all_true(S < B, 1) payoff = af.maxof(0, S - K) - return af.mean(payoff) * math.exp(-r * t) + mean_payoff = cast(float, af.mean(payoff)) * math.exp(-r * t) + + return mean_payoff -def monte_carlo_simulate(N, use_barrier, num_iter=10): +def monte_carlo_simulate(N: int, use_barrier: bool, num_iter: int = 10) -> float: steps = 180 stock_price = 100.0 maturity = 0.5 volatility = 0.3 rate = 0.01 strike = 100 - barrier = 115.0 + barrier = 115.0 if use_barrier else None - start = time() - for i in range(num_iter): + total_time = time() + for _ in range(num_iter): monte_carlo_options(N, stock_price, maturity, volatility, rate, strike, steps, use_barrier, barrier) + average_time = (time() - total_time) / num_iter - return (time() - start) / num_iter + return average_time -if __name__ == "__main__": +def main() -> None: if len(sys.argv) > 1: - af.set_device(int(sys.argv[1])) + device_id = int(sys.argv[1]) + af.set_device(device_id) af.info() - monte_carlo_simulate(1000, use_barrier=False) - monte_carlo_simulate(1000, use_barrier=True) - af.sync() + # Initial simulation calls to test without and with barrier + print("Simulation without barrier:", monte_carlo_simulate(1000, use_barrier=False)) + print("Simulation with barrier:", monte_carlo_simulate(1000, use_barrier=True)) + + af.sync() # Synchronize ArrayFire computations before timing analysis + # Timing analysis for different numbers of paths for n in range(10000, 100001, 10000): + time_vanilla = 1000 * monte_carlo_simulate(n, False, 100) + time_barrier = 1000 * monte_carlo_simulate(n, True, 100) print( - "Time for %7d paths - vanilla method: %4.3f ms, barrier method: % 4.3f ms\n" - % (n, 1000 * monte_carlo_simulate(n, False, 100), 1000 * monte_carlo_simulate(n, True, 100)) + f"Time for {n:7d} paths - vanilla method: {time_vanilla:4.3f} ms, barrier method: {time_barrier:4.3f} ms" ) + + +if __name__ == "__main__": + main() diff --git a/examples/getting_started/convolve.py b/examples/getting_started/convolve.py index 22d87ec..f98f4ab 100644 --- a/examples/getting_started/convolve.py +++ b/examples/getting_started/convolve.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2024, ArrayFire @@ -14,35 +14,64 @@ from timeit import timeit import arrayfire as af -from arrayfire.library.signal_processing import convolve2, convolve2_separable -def af_assert(left, right, eps=1e-6): - if af.max(af.abs(left - right)) > eps: - raise ValueError("Arrays not within dictated precision") - return - - -if __name__ == "__main__": +def set_device_from_args() -> None: + """Sets the ArrayFire device based on the command line argument.""" if len(sys.argv) > 1: af.set_device(int(sys.argv[1])) af.info() + +def create_arrays() -> tuple[af.Array, ...]: + """Creates and returns initialized ArrayFire arrays for convolution.""" h_dx = array("f", (1.0 / 12, -8.0 / 12, 0, 8.0 / 12, 1.0 / 12)) h_spread = array("f", (1.0 / 5, 1.0 / 5, 1.0 / 5, 1.0 / 5, 1.0 / 5)) img = af.randu((640, 480)) dx = af.Array(h_dx, shape=(5, 1)) spread = af.Array(h_spread, shape=(1, 5)) + + return img, dx, spread + + +def perform_convolution(img: af.Array, dx: af.Array, spread: af.Array) -> tuple[af.Array, af.Array]: + """Performs and returns the result of full and separable 2D convolution.""" kernel = af.matmul(dx, spread) + full_res = af.convolve2(img, kernel) + sep_res = af.convolve2_separable(dx, spread, img) + return full_res, sep_res - full_res = convolve2(img, kernel) - sep_res = convolve2_separable(dx, spread, img) - af_assert(full_res, sep_res) +def af_assert(left: af.Array, right: af.Array, eps: float = 1e-6) -> None: + """Asserts that two arrays are equal within a specified precision.""" + max_diff = af.max(af.abs(left - right)) + if isinstance(max_diff, complex): + max_diff = max_diff.real + if max_diff > eps: + raise ValueError("Arrays not within dictated precision") + + +def time_convolution_operations(img: af.Array, dx: af.Array, spread: af.Array, kernel: af.Array) -> None: + """Times and prints the convolution operations.""" + time_convolve2 = timeit(lambda: af.convolve2(img, kernel), number=1000) + time_convolve2_sep = timeit(lambda: af.convolve2_separable(dx, spread, img), number=1000) + + print(f"Full 2D convolution time: {time_convolve2 * 1000:.5f} ms") + print(f"Full separable 2D convolution time: {time_convolve2_sep * 1000:.5f} ms") - time_convolve2 = timeit(lambda: convolve2(img, kernel), number=1000) - print(f"full 2D convolution time: {time_convolve2 * 1000:.5f} ms") - time_convolve2_sep = timeit(lambda: convolve2_separable(dx, spread, img), number=1000) - print(f"full separable 2D convolution time: {time_convolve2_sep * 1000:.5f} ms") +def main() -> None: + try: + set_device_from_args() + img, dx, spread = create_arrays() + full_res, sep_res = perform_convolution(img, dx, spread) + af_assert(full_res, sep_res) + kernel = af.matmul(dx, spread) # Reconstruct kernel for timing + time_convolution_operations(img, dx, spread, kernel) + except Exception as e: + print(f"Error: {str(e)}") + + +if __name__ == "__main__": + main() diff --git a/examples/getting_started/intro.py b/examples/getting_started/intro.py index d921e23..7f99bda 100644 --- a/examples/getting_started/intro.py +++ b/examples/getting_started/intro.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python ####################################################### # Copyright (c) 2024, ArrayFire @@ -14,20 +14,32 @@ import arrayfire as af -if __name__ == "__main__": + +def set_device_from_args() -> None: + """Sets the ArrayFire device based on command line argument.""" if len(sys.argv) > 1: af.set_device(int(sys.argv[1])) af.info() - print("\n---- Intro to ArrayFire using unsigned(s32) arrays ----\n") +def initialize_matrices() -> tuple[af.Array, ...]: + """Initializes matrices for demonstration.""" h_A = array("i", (1, 2, 4, -1, 2, 0, 4, 2, 3)) h_B = array("i", (2, 3, 5, 6, 0, 10, -12, 0, 1)) - A = af.Array(obj=h_A, shape=(3, 3), dtype=af.int32) B = af.Array(obj=h_B, shape=(3, 3), dtype=af.int32) - print("\n---- Sub referencing and sub assignment\n") + b_A = array("I", (1, 1, 1, 0, 1, 1, 0, 0, 0)) + b_B = array("I", (1, 0, 1, 0, 1, 0, 1, 0, 1)) + C = af.Array(obj=b_A, shape=(3, 3), dtype=af.uint32) + D = af.Array(obj=b_B, shape=(3, 3), dtype=af.uint32) + + return A, B, C, D + + +def demonstrate_array_operations(A: af.Array, B: af.Array, C: af.Array, D: af.Array) -> None: + """Performs and prints various ArrayFire operations.""" + print("\n---- Sub referencing and sub assignment ----\n") print(A) print(A[0, :]) print(A[:, 0]) @@ -38,40 +50,46 @@ A[1, :] = B[2, :] print(A) - b_A = array("I", (1, 1, 1, 0, 1, 1, 0, 0, 0)) - b_B = array("I", (1, 0, 1, 0, 1, 0, 1, 0, 1)) - - C = af.Array(obj=b_A, shape=(3, 3), dtype=af.uint32) - D = af.Array(obj=b_B, shape=(3, 3), dtype=af.uint32) + print("\n---- Bitwise operations ----\n") print(C) print(D) - - print("\n---- Bitwise operations\n") print(af.bitand(C, D)) print(af.bitor(C, D)) - print(C, D) - print("\n---- Transpose\n") + print("\n---- Transpose ----\n") print(A) print(af.transpose(A)) - print("\n---- Flip Vertically / Horizontally\n") + print("\n---- Flip Vertically / Horizontally ----\n") print(A) print(af.flip(A, axis=0)) print(af.flip(A, axis=1)) - print("\n---- Sum, Min, Max along row / columns\n") + print("\n---- Sum, Min, Max along row / columns ----\n") print(A) - print(af.min(A, axis=0)) # type: ignore[arg-type] - print(af.max(A, axis=0)) # type: ignore[arg-type] + print(af.min(A, axis=0)) + print(af.max(A, axis=0)) + print(af.min(A, axis=1)) + print(af.max(A, axis=1)) + print(af.sum(A, axis=0)) + print(af.sum(A, axis=1)) + + print("\n---- Get minimum with index ----\n") + (min_val, min_idx) = af.imin(A, axis=0) + print(min_val) + print(min_idx) - print(af.min(A, axis=1)) # type: ignore[arg-type] - print(af.max(A, axis=1)) # type: ignore[arg-type] - print(af.sum(A, axis=0)) # type: ignore[arg-type] - print(af.sum(A, axis=1)) # type: ignore[arg-type] +def main() -> None: + """Main function to orchestrate the initialization and demonstration.""" + try: + set_device_from_args() + print("\n---- Intro to ArrayFire using unsigned(s32) arrays ----\n") + A, B, C, D = initialize_matrices() + demonstrate_array_operations(A, B, C, D) + except Exception as e: + print("Error:", str(e)) - print("\n---- Get minimum with index\n") - (min_val, min_idx) = af.imin(A, axis=0) - print(min_val) # type: ignore[arg-type] - print(min_idx) # type: ignore[arg-type] + +if __name__ == "__main__": + main() diff --git a/examples/lin_algebra/cholesky.py b/examples/lin_algebra/cholesky.py deleted file mode 100644 index 8c5272a..0000000 --- a/examples/lin_algebra/cholesky.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python -####################################################### -# Copyright (c) 2024, ArrayFire -# All rights reserved. -# -# This file is distributed under 3-clause BSD license. -# The complete license agreement can be obtained at: -# http://arrayfire.com/licenses/BSD-3-Clause -######################################################## - -import arrayfire as af - - -def main(): - try: - af.info() - - n = 5 - t = af.randu((n, n)) - arr_in = af.matmul(t, t, rhs_opts=af.MatProp.TRANS) + af.identity((n, n)) * n - - print("Running Cholesky InPlace\n") - cin_upper = arr_in.copy() - cin_lower = arr_in.copy() - - af.cholesky(cin_upper, True) - af.cholesky(cin_lower, False) - - print(cin_upper) - print(cin_lower) - - print("\nRunning Cholesky Out of place\n") - - out_upper, upper_success = af.cholesky(arr_in, True) - out_lower, lower_success = af.cholesky(arr_in, False) - - if upper_success == 0: - print(out_upper) - if lower_success == 0: - print(out_lower) - - except Exception as e: - print("Error: ", str(e)) - - -if __name__ == "__main__": - main() diff --git a/examples/lin_algebra/lu.py b/examples/lin_algebra/lu.py deleted file mode 100644 index 0fe69b6..0000000 --- a/examples/lin_algebra/lu.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python -####################################################### -# Copyright (c) 2024, ArrayFire -# All rights reserved. -# -# This file is distributed under 3-clause BSD license. -# The complete license agreement can be obtained at: -# http://arrayfire.com/licenses/BSD-3-Clause -######################################################## - -import arrayfire as af - - -def main(): - try: - af.info() - - in_array = af.randu((5, 8)) - - print("Running LU InPlace\n") - pivot = af.lu(in_array, inplace=True) - print(in_array) - print(pivot) - - print("Running LU with Upper Lower Factorization\n") - lower, upper, pivot = af.lu(in_array) - print(lower) - print(upper) - print(pivot) - - except Exception as e: - print("Error: ", str(e)) - - -if __name__ == "__main__": - main() diff --git a/examples/lin_algebra/qr.py b/examples/lin_algebra/qr.py deleted file mode 100644 index 0531420..0000000 --- a/examples/lin_algebra/qr.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -####################################################### -# Copyright (c) 2024, ArrayFire -# All rights reserved. -# -# This file is distributed under 3-clause BSD license. -# The complete license agreement can be obtained at: -# http://arrayfire.com/licenses/BSD-3-Clause -######################################################## - -import arrayfire as af - - -def main(): - try: - af.info() - in_array = af.randu((5, 8)) - - print("Running QR InPlace\n") - q_in = in_array.copy() - print(q_in) - - tau = af.qr(q_in, inplace=True) - - print(q_in) - print(tau) - - print("Running QR with Q and R factorization\n") - q, r, tau = af.qr(in_array) - - print(q) - print(r) - print(tau) - - except Exception as e: - print("Error: ", str(e)) - - -if __name__ == "__main__": - main() diff --git a/examples/linear_algebra/cholesky.py b/examples/linear_algebra/cholesky.py new file mode 100644 index 0000000..4c34067 --- /dev/null +++ b/examples/linear_algebra/cholesky.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2024, 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 generate_symmetric_positive_definite_matrix(n: int) -> af.Array: + """Generates a symmetric positive definite matrix of size n x n.""" + t = af.randu((n, n)) + return af.matmul(t, t, rhs_opts=af.MatProp.TRANS) + af.identity((n, n)) * n + + +def run_cholesky_inplace(matrix: af.Array) -> None: + """Performs Cholesky decomposition in place and prints the upper and lower triangular results.""" + print("Running Cholesky InPlace") + cin_upper = matrix.copy() + cin_lower = matrix.copy() + + af.cholesky(cin_upper, is_upper=True) + af.cholesky(cin_lower, is_upper=False) + + print(cin_upper) + print(cin_lower) + + +def run_cholesky_out_of_place(matrix: af.Array) -> None: + """Performs Cholesky decomposition out of place and prints the results if successful.""" + print("Running Cholesky Out of place") + + out_upper, upper_success = af.cholesky(matrix, is_upper=True) + out_lower, lower_success = af.cholesky(matrix, is_upper=False) + + if upper_success == 0: + print("Upper triangular matrix:") + print(out_upper) + if lower_success == 0: + print("Lower triangular matrix:") + print(out_lower) + + +def main() -> None: + try: + af.info() + n = 5 + spd_matrix = generate_symmetric_positive_definite_matrix(n) + + run_cholesky_inplace(spd_matrix) + run_cholesky_out_of_place(spd_matrix) + + except Exception as e: + print("Error: ", str(e)) + + +if __name__ == "__main__": + main() diff --git a/examples/linear_algebra/lu.py b/examples/linear_algebra/lu.py new file mode 100644 index 0000000..8b8a42e --- /dev/null +++ b/examples/linear_algebra/lu.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2024, 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 run_lu_inplace(array: af.Array) -> None: + """Performs LU decomposition in place and prints the results.""" + print("Running LU InPlace") + pivot = af.lu(array, inplace=True) + print(array) + print(pivot) + + +def run_lu_factorization(array: af.Array) -> None: + """Performs LU decomposition, extracting and printing Lower and Upper matrices.""" + print("Running LU with Upper Lower Factorization") + lower, upper, pivot = af.lu(array) + print(lower) + print(upper) + print(pivot) + + +def main() -> None: + try: + af.info() # Display ArrayFire library information + in_array = af.randu((5, 8)) # Generate a random 5x8 matrix + + # Perform and print results of LU decomposition in place + run_lu_inplace(in_array.copy()) # Use a copy to preserve the original matrix for the next function + # Perform and print results of LU decomposition with L and U matrices + run_lu_factorization(in_array) + + except Exception as e: + print("Error: ", str(e)) + + +if __name__ == "__main__": + main() diff --git a/examples/linear_algebra/qr.py b/examples/linear_algebra/qr.py new file mode 100644 index 0000000..9a6eb3b --- /dev/null +++ b/examples/linear_algebra/qr.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +####################################################### +# Copyright (c) 2024, 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 run_qr_inplace(array: af.Array) -> None: + """Performs QR decomposition in place and prints the results.""" + print("Running QR InPlace") + q_in = array.copy() + tau = af.qr(q_in, inplace=True) + print(q_in) + print(tau) + + +def run_qr_factorization(array: af.Array) -> None: + """Performs QR decomposition, extracting and printing Q and R matrices.""" + print("Running QR with Q and R factorization") + q, r, tau = af.qr(array) + print(q) + print(r) + print(tau) + + +def main() -> None: + try: + af.info() + in_array = af.randu((5, 8)) # Random 5x8 matrix + + run_qr_inplace(in_array) + run_qr_factorization(in_array) + + except Exception as e: + print("Error: ", str(e)) + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index 7f5a525..49db3af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,9 @@ classifiers = [ Homepage = "http://arrayfire.com" "General Documentation" = "https://arrayfire.org/docs/index.htm" +[project.optional-dependencies] +benchmarks = ["numpy ~= 1.26.4"] + [project.entry-points.array_api] array_api = "arrayfire.array_api" From a35077389827cd0f931573d33545afbf2e0d920e Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 17 Apr 2024 02:07:57 +0300 Subject: [PATCH 2/3] Apply changes --- examples/financial/heston_model.py | 36 ++++++++++++++++++----- examples/financial/monte_carlo_options.py | 2 +- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/examples/financial/heston_model.py b/examples/financial/heston_model.py index 443372d..ff065c6 100644 --- a/examples/financial/heston_model.py +++ b/examples/financial/heston_model.py @@ -1,13 +1,35 @@ #!/usr/bin/env python -####################################################### -# Copyright (c) 2024, ArrayFire +############################################################################################## +# Copyright (c) 2015, Michael Nowotny # 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 -######################################################## +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used +# to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################################### import math import time @@ -46,7 +68,7 @@ def simulate_heston_model( v = [af.constant(v0, (R,)) for _ in range(2)] for t in range(1, N): - t_previous = (t - 1) % 2 + t_previous = (t + 1) % 2 t_current = t % 2 dBt = af.randn((R, 2)) * sqrtDeltaT diff --git a/examples/financial/monte_carlo_options.py b/examples/financial/monte_carlo_options.py index eed480a..d5cfcb8 100644 --- a/examples/financial/monte_carlo_options.py +++ b/examples/financial/monte_carlo_options.py @@ -33,7 +33,7 @@ def monte_carlo_options( s = af.constant(strike, (N, 1), dtype=ty) randmat = af.randn((N, steps - 1), dtype=ty) - randmat = af.exp((r - (vol**2 * 0.5)) * dt + vol * math.sqrt(dt) * randmat) + randmat = af.exp((r - (vol * vol * 0.5)) * dt + vol * math.sqrt(dt) * randmat) S = af.product(af.join(1, s, randmat), axis=1) From 269ce50de2118741163653347045c82c36944a08 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 17 Apr 2024 02:09:19 +0300 Subject: [PATCH 3/3] Fix typing --- examples/financial/heston_model.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/financial/heston_model.py b/examples/financial/heston_model.py index ff065c6..482eeb8 100644 --- a/examples/financial/heston_model.py +++ b/examples/financial/heston_model.py @@ -33,12 +33,11 @@ import math import time -from typing import Tuple import arrayfire as af -def initialize_parameters() -> Tuple[float, float, float, float, float, float, float, float]: +def initialize_parameters() -> tuple[float, float, float, float, float, float, float, float]: """Initialize and return model parameters.""" r = math.log(1.0319) # risk-free rate rho = -0.82 # instantaneous correlation between Brownian motions @@ -53,7 +52,7 @@ def initialize_parameters() -> Tuple[float, float, float, float, float, float, f def simulate_heston_model( T: int, N: int, R: int, mu: float, kappa: float, vBar: float, sigmaV: float, rho: float, x0: float, v0: float -) -> Tuple[af.Array, af.Array]: +) -> tuple[af.Array, af.Array]: """Simulate the Heston model for given parameters and return the resulting arrays.""" deltaT = T / (N - 1) sqrtDeltaT = math.sqrt(deltaT)