Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 28 additions & 19 deletions arrayfire/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,9 +533,9 @@
trunc,
)

__all__ += ["randu"]
__all__ += ["randn", "randu"]

from arrayfire.library.random import randu
from arrayfire.library.random import randn, randu

__all__ += [
"fft",
Expand Down Expand Up @@ -591,23 +591,26 @@

from arrayfire.library.statistics import corrcoef, cov, mean, median, stdev, topk, var

__all__ += [
"get_active_backend",
"get_available_backends",
"get_backend_count",
"get_backend_id",
"get_device_id",
"set_backend",
]

from arrayfire.library.unified_api_functions import (
get_active_backend,
get_available_backends,
get_backend_count,
get_backend_id,
get_device_id,
set_backend,
)
# TODO
# Temp solution. Remove when arrayfire-binary-python-wrapper is finalized

# __all__ += [
# "get_active_backend",
# "get_available_backends",
# "get_backend_count",
# "get_backend_id",
# "get_device_id",
# "set_backend",
# ]

# from arrayfire.library.unified_api_functions import (
# get_active_backend,
# get_available_backends,
# get_backend_count,
# get_backend_id,
# get_device_id,
# set_backend,
# )

__all__ += [
"accum",
Expand Down Expand Up @@ -656,3 +659,9 @@
__all__ += ["cast"]

from arrayfire.library.utils import cast

# Backend

__all__ += ["set_backend", "get_backend", "BackendType"]

from arrayfire_wrapper import BackendType, get_backend, set_backend
1 change: 1 addition & 0 deletions arrayfire/library/array_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ def copy_array(array: Array, /) -> Array:
def eval(*arrays: Array) -> None:
if len(arrays) == 1:
wrapper.eval(arrays[0].arr)
return

arrs = [array.arr for array in arrays]
wrapper.eval_multiple(len(arrays), *arrs)
Expand Down
134 changes: 130 additions & 4 deletions arrayfire/library/linear_algebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,43 @@ def dot(
lhs: Array,
rhs: Array,
/,
lhs_opts: MatProp = MatProp.NONE,
rhs_opts: MatProp = MatProp.NONE,
*,
return_scalar: bool = False,
) -> int | float | complex | Array:
"""
Calculates the dot product of two input arrays, with options to modify the operation
on the input arrays and the possibility to return the result as a scalar.

Parameters
----------
lhs : Array
A 1-dimensional, int of float Array instance, representing an array.

rhs : Array
A 1-dimensional, int of float Array instance, representing another array.

return_scalar : bool, optional
When set to True, the input arrays are flattened, and the output is a scalar value.
Default is False.

Returns
-------
out : int | float | complex | Array
The result of the dot product. Returns an Array unless `return_scalar` is True,
in which case a scalar value (int, float, or complex) is returned based on the
data type of the inputs.

Note
-----
- The data types of `lhs` and `rhs` should be the same.
- Batch operations are not supported.
- Modification options for `lhs` and `rhs` are currently disabled as function supports only `MatProp.NONE`.
"""
# TODO
# Add support of lhs_opts and rhs_opts and return them as key arguments.
lhs_opts: MatProp = MatProp.NONE
rhs_opts: MatProp = MatProp.NONE

if return_scalar:
return wrapper.dot_all(lhs.arr, rhs.arr, lhs_opts, rhs_opts)

Expand All @@ -50,11 +82,105 @@ def gemm(
alpha: int | float = 1.0,
beta: int | float = 0.0,
) -> Array:
"""
Performs BLAS general matrix multiplication (GEMM) on two Array instances.

The operation is defined as: C = alpha * op(lhs) * op(rhs) + beta * C, where op(X) is
one of no operation, transpose, or Hermitian transpose, determined by lhs_opts and rhs_opts.

Parameters
----------
lhs : Array
A 2-dimensional, real or complex array representing the left-hand side matrix.

rhs : Array
A 2-dimensional, real or complex array representing the right-hand side matrix.

lhs_opts : MatProp, optional
Operation to perform on `lhs` before multiplication. Default is MatProp.NONE. Options include:
- MatProp.NONE: No operation.
- MatProp.TRANS: Transpose.
- MatProp.CTRANS: Hermitian transpose.

rhs_opts : MatProp, optional
Operation to perform on `rhs` before multiplication. Default is MatProp.NONE. Options include:
- MatProp.NONE: No operation.
- MatProp.TRANS: Transpose.
- MatProp.CTRANS: Hermitian transpose.

alpha : int | float, optional
Scalar multiplier for the product of `lhs` and `rhs`. Default is 1.0.

beta : int | float, optional
Scalar multiplier for the existing matrix C in the accumulation. Default is 0.0.

Returns
-------
Array
The result of the matrix multiplication operation.

Note
-----
- The data types of `lhs` and `rhs` must be compatible.
- Batch operations are not supported in this version.
"""
return cast(Array, wrapper.gemm(lhs.arr, rhs.arr, lhs_opts, rhs_opts, alpha, beta))


@afarray_as_array
def matmul(lhs: Array, rhs: Array, /, lhs_opts: MatProp = MatProp.NONE, rhs_opts: MatProp = MatProp.NONE) -> Array:
"""
Performs generalized matrix multiplication between two arrays with optional
transposition or hermitian transposition operations on the input matrices.

Parameters
----------
lhs : af.Array
A 2-dimensional, real or complex ArrayFire array representing the left-hand side matrix.

rhs : af.Array
A 2-dimensional, real or complex ArrayFire array representing the right-hand side matrix.

lhs_opts : af.MATPROP, optional
Operation to perform on the `lhs` matrix before multiplication. Defaults to af.MATPROP.NONE.
Options include:
- af.MATPROP.NONE: No operation.
- af.MATPROP.TRANS: Transpose `lhs`.
- af.MATPROP.CTRANS: Hermitian transpose (conjugate transpose) `lhs`.

rhs_opts : af.MATPROP, optional
Operation to perform on the `rhs` matrix before multiplication. Defaults to af.MATPROP.NONE.
Options include:
- af.MATPROP.NONE: No operation.
- af.MATPROP.TRANS: Transpose `rhs`.
- af.MATPROP.CTRANS: Hermitian transpose (conjugate transpose) `rhs`.

Returns
-------
out : af.Array
The result of the matrix multiplication. The output is a 2-dimensional ArrayFire array.

Notes
-----
- The data types of `lhs` and `rhs` must be the same.
- Batch operations (multiplying multiple pairs of matrices at once) are not supported in this implementation.

Examples
--------
Basic matrix multiplication:

A = af.randu(5, 4, dtype=af.Dtype.f32)
B = af.randu(4, 6, dtype=af.Dtype.f32)
C = matmul(A, B)

Matrix multiplication with the left-hand side transposed:

C = matmul(A, B, lhs_opts=af.MATPROP.TRANS)

Matrix multiplication with both matrices transposed:

C = matmul(A, B, lhs_opts=af.MATPROP.TRANS, rhs_opts=af.MATPROP.TRANS)
"""
return cast(Array, wrapper.matmul(lhs.arr, rhs.arr, lhs_opts, rhs_opts))


Expand Down Expand Up @@ -121,5 +247,5 @@ def solve(a: Array, b: Array, /, *, options: MatProp = MatProp.NONE, pivot: None
return cast(Array, wrapper.solve(a.arr, b.arr, options))


# TODO
# Add Sparse functions? #good_first_issue
# TODO #good_first_issue
# Add Sparse functions
46 changes: 46 additions & 0 deletions arrayfire/library/random.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,52 @@ def from_engine(cls, engine: wrapper.AFRandomEngineHandle) -> RandomEngine:
return instance


@afarray_as_array
def randn(shape: tuple[int, ...], /, *, dtype: Dtype = float32, engine: RandomEngine | None = None) -> Array:
"""
Create a multi-dimensional array containing values sampled from a normal distribution with mean 0
and standard deviation of 1.

Parameters
----------
shape : tuple[int, ...]
The shape of the resulting array. Must be a tuple with at least one element, e.g., shape=(3,).

dtype : Dtype, optional, default: `float32`
The data type of the array elements.

engine : RandomEngine | None, optional
The random number generator engine to be used. If None, uses a default engine created by ArrayFire.

Returns
-------
Array
A multi-dimensional array whose elements are sampled from a normal distribution. The dimensions of the array
are determined by `shape`:
- If shape is (x,), the output is a 1D array of size (x,).
- If shape is (x, y), the output is a 2D array of size (x, y).
- If shape is (x, y, z), the output is a 3D array of size (x, y, z).
- For more dimensions, the output shape corresponds directly to the specified `shape` tuple.

Notes
-----
The function supports creating arrays of up to N dimensions, where N is determined by the length
of the `shape` tuple.

Raises
------
ValueError
If `shape` is not a tuple or has less than one value.
"""
if not isinstance(shape, tuple) or not shape:
raise ValueError("Argument shape must be a tuple with at least 1 value.")

if engine is None:
return cast(Array, wrapper.randn(shape, dtype))

return cast(Array, wrapper.random_normal(shape, dtype, engine.get_engine()))


@afarray_as_array
def randu(shape: tuple[int, ...], /, *, dtype: Dtype = float32, engine: RandomEngine | None = None) -> Array:
"""
Expand Down
37 changes: 20 additions & 17 deletions arrayfire/library/unified_api_functions.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
__all__ = [
"get_active_backend",
"get_available_backends",
"get_backend_count",
"get_backend_id",
"get_device_id",
"set_backend",
]
# TODO
# Temp solution. Remove when arrayfire-binary-python-wrapper is finalized

from arrayfire_wrapper.lib import get_active_backend, get_available_backends, get_backend_count
from arrayfire_wrapper.lib import get_backend_id as wrapped_get_backend_id
from arrayfire_wrapper.lib import get_device_id as wrapped_get_device_id
from arrayfire_wrapper.lib import set_backend
# __all__ = [
# "get_active_backend",
# "get_available_backends",
# "get_backend_count",
# "get_backend_id",
# "get_device_id",
# "set_backend",
# ]

from arrayfire import Array
# from arrayfire_wrapper.lib import get_active_backend, get_available_backends, get_backend_count
# from arrayfire_wrapper.lib import get_backend_id as wrapped_get_backend_id
# from arrayfire_wrapper.lib import get_device_id as wrapped_get_device_id
# from arrayfire_wrapper.lib import set_backend

# from arrayfire import Array

def get_backend_id(array: Array) -> int:
return wrapped_get_backend_id(array.arr)

# def get_backend_id(array: Array) -> int:
# return wrapped_get_backend_id(array.arr)

def get_device_id(array: Array) -> int:
return wrapped_get_device_id(array.arr)

# def get_device_id(array: Array) -> int:
# return wrapped_get_device_id(array.arr)
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
arrayfire-python-wrapper==0.5.0+af3.9.0
arrayfire-binary-python-wrapper==0.6.0+af3.9.0
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def fix_url_dependencies(req: str) -> str:
name="arrayfire",
version=VERSION["VERSION"],
description="ArrayFire Python Wrapper",
licence="BSD",
license="BSD",
long_description=(ABS_PATH / "README.md").open("r").read(),
long_description_content_type="text/markdown",
author="ArrayFire",
Expand Down
12 changes: 12 additions & 0 deletions tests/_helpers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
import arrayfire as af


def round_to(list_: list[int | float | complex | bool], symbols: int = 3) -> list[int | float]:
# HACK replace for e.g. abs(x1-x2) < 1e-6 ~ https://davidamos.dev/the-right-way-to-compare-floats-in-python/
return [round(x, symbols) for x in list_]


def create_from_2d_nested(x1: float, x2: float, x3: float, x4: float, dtype: af.Dtype = af.float32) -> af.Array:
array = af.randu((2, 2), dtype=dtype)
array[0, 0] = x1
array[0, 1] = x2
array[1, 0] = x3
array[1, 1] = x4
return array
Loading