###########################################################################################
# Copyright ironArray SL 2021.
#
# All rights reserved.
#
# This software is the confidential and proprietary information of ironArray SL
# ("Confidential Information"). You shall not disclose such Confidential Information
# and shall use it only in accordance with the terms of the license agreement.
###########################################################################################
from __future__ import annotations
import iarray as ia
from iarray import iarray_ext as ext
import numpy as np
from typing import (
Any,
Literal,
Optional,
Sequence,
Tuple,
Union,
)
from .dtypes import (
_all_dtypes,
_boolean_dtypes,
_integer_dtypes,
_integer_or_boolean_dtypes,
_floating_dtypes,
_numeric_dtypes,
_dtype_categories,
)
from enum import IntEnum
import ndindex
from .info import InfoReporter
PyCapsule = Any
Device = Literal["cpu"]
class OIndex:
def __init__(self, array):
self.array = array
def __getitem__(self, selection):
return self.array.get_orthogonal_selection(selection)
def __setitem__(self, selection, value):
return self.array.set_orthogonal_selection(selection, value)
def process_selection(selection, shape):
mask = tuple(True if isinstance(s, int) else False for s in selection)
new_selection = []
for s, n in zip(selection, shape):
if isinstance(s, slice):
si = np.array([i for i in range(*s.indices(n))])
elif isinstance(s, int):
si = np.array([s % n])
else:
si = np.array([i % n for i in s])
new_selection.append(si)
return new_selection
def process_key(key, shape):
key = ndindex.ndindex(key).expand(shape).raw
mask = tuple(True if isinstance(k, int) else False for k in key)
key = tuple(k if isinstance(k, slice) else slice(k, k + 1, None) for k in key)
return key, mask
def is_documented_by(original):
def wrapper(target):
target.__doc__ = original.__doc__
return target
return wrapper
# For avoiding a warning in PyCharm in method signatures
IArray = None
class IArray(ext.Container):
"""The ironArray data container.
This is not meant to be called from user space.
"""
@classmethod
def cast(cls, cont):
cont.__class__ = cls
assert isinstance(cont, IArray)
return cont
@property
def info(self):
"""
Print information about this array.
"""
return InfoReporter(self)
@property
def info_items(self):
items = []
items += [("type", self.__class__.__name__)]
items += [("shape", self.shape)]
items += [("chunks", self.chunks)]
items += [("blocks", self.blocks)]
items += [("cratio", f"{self.cratio:.2f}")]
return items
@property
def data(self):
"""
Get a ndarray with array data.
Returns
-------
out: `np.ndarray `_
"""
return ia.iarray2numpy(self)
@property
def attrs(self):
return ia.Attributes(self)
@property
def oindex(self):
return OIndex(self)
def split(self):
"""Split the array in a list of one-chunk array.
Returns
-------
A list with one-chunk arrays.
"""
with ia.config() as cfg:
return ext.split(cfg, self)
def slice_chunk_index(self, shape: Sequence, chunk_index: list):
"""Slice the array using chunk indexes.
Parameters
----------
shape: Sequence
The shape of the result.
chunk_index: lsit
The indexes of the chunks that will create the slice.
Returns
-------
:ref:`IArray`
A new array containing the chunk that are specified.
"""
with ia.config() as cfg:
return ext.from_chunk_index(cfg, self, shape, chunk_index)
@property
def device(self) -> Device:
"""
Hardware device where the array data resides on.
"""
return "cpu"
@property
def mT(self) -> IArray:
raise NotImplementedError("IArray.mT is not supported yet")
@property
def size(self) -> int:
"""
Number of elements in the array.
"""
return int(np.prod(self.shape))
def _check_allowed_dtypes(
self, value: bool | int | float | IArray, dtype_category: str, op: str
) -> IArray:
if self.dtype not in _dtype_categories[dtype_category]:
raise TypeError(f"Only {dtype_category} dtypes are allowed in {op}")
if isinstance(value, IArray):
if value.dtype not in _dtype_categories[dtype_category]:
raise TypeError(f"Only {dtype_category} dtypes are allowed in {op}")
elif not isinstance(value, (int, float, bool, ia.LazyExpr)):
raise RuntimeError("Expected bool, int, float, LazyExpr or IArray instance")
def copy(self, cfg=None, **kwargs) -> IArray:
"""Return a copy of the array.
Parameters
----------
cfg : :class:`Config`
The configuration for this operation. If None (default), the
configuration from self will be used instead of that of the current configuration.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config`
dataclass that should override the configuration.
By default, this function deactivates btune unless it is specified.
Returns
-------
:ref:`IArray`
The copy.
"""
if cfg is None:
cfg = self.cfg
# the urlpath should not be copied
cfg.urlpath = None
# Generally we don't want btune to optimize, except if specified
btune = False
if "favor" in kwargs and "btune" not in kwargs:
btune = True
if "btune" in kwargs:
btune = kwargs["btune"]
kwargs.pop("btune")
with ia.config(shape=self.shape, cfg=cfg, btune=btune, **kwargs) as cfg:
return ext.copy(cfg, self)
def copyto(self, dest):
"""Copy array contents to `dest`.
Parameters
----------
dest : Any
The destination container. It can be any object that supports
multidimensional assignment (NumPy, Zarr, HDF5...). It should have the same
shape than `self`.
"""
if tuple(dest.shape) != self.shape:
raise IndexError("Incompatible destination shape")
for info, block in self.iter_read_block():
dest[info.slice] = block[:]
def resize(self, newshape, start=None):
"""Change the shape of the array by growing or shrinking one or more dimensions.
Parameters
----------
newshape : tuple or list
The new shape of the array container. It should have the same dimensions
as `self`.
start: tuple, list or None, optional.
The position from where the array will be extended or shrunk according to
:paramref:`newshape`. If given, it should have the same dimensions
as `self`. If None (the default), the appended or deleted chunks will happen
at the end of the array.
Notes
-----
The array values corresponding to the added positions are not initialized.
Thus, the user is in charge of initializing them.
Furthermore, the :paramref:`start` has to fulfill the same conditions than in
:func:`insert`, :func:`append` and :func:`delete`.
See Also
--------
insert
append
delete
"""
ext.resize(self, new_shape=newshape, start=start)
return self.shape
def insert(self, data, axis=0, start=None):
"""Insert data in a position by extending the :paramref:`axis`.
Parameters
----------
data: object supporting the PyBuffer protocol
The object containing the data.
axis: int, optional
The axis along the data contained by :paramref:`data` will be inserted.
Default is 0.
start: int, optional
The position in the array axis from where to start inserting the data.
If None (default), it will be appended at the end.
Notes
-----
If :paramref:`start` is not at the end of the array, it must be a multiple of `chunks[axis]`.
Furthermore, if `start != shape[axis]` the number of elements of :paramref:`data`
must be a multiple of `chunks[axis] * shape[in the other axis]` and
if `start = shape[axis]` (or `None`) the number of elements of :paramref:`data`
must be a multiple of `shape[in the other axis]`.
For example, letâs suppose that we have an array of `shape = [20, 20]` and `chunks = [7,7]`,
and we would like to insert data in the `axis = 0`. Then, if `start = 0`
which is different from `shape[axis]` and multiple of `chunks[axis]`,
the number of elements of :paramref:`data` must be a multiple of `7 * 20`.
If `start = 20 = shape[axis]` (or None), the number of elements of :paramref:`data`
can be `anything * 20`.
See Also
--------
append
delete
resize
"""
if type(data) is np.ndarray:
if data.dtype.itemsize != np.dtype(self.dtype).itemsize:
data = np.array(data, dtype=self.dtype)
elif data.dtype.str[0] == ">":
data = data.byteswap()
ext.insert(self, data, axis, start)
return self.shape
def append(self, data, axis=0):
"""Append data at the end by extending the :paramref:`axis`.
Parameters
----------
data: object supporting the PyBuffer protocol
The object containing the data.
axis: int, optional
Axis along which to append.
Default is 0.
Notes
-----
The number of elements of :paramref:`data` must be a multiple of the array shape in all its axis
excluding the :paramref:`axis`.
For example, letâs suppose that we have an array of `shape = [20, 20]`, and `chunks = [7, 7]`.
Then number of elements of :paramref:`data` can be `anything * 20` and the new shape would be
`[20 + anything, 20]`.
See Also
--------
insert
delete
resize
"""
if type(data) is np.ndarray:
if data.dtype.itemsize != np.dtype(self.dtype).itemsize:
data = np.array(data, dtype=self.dtype)
elif data.dtype.str[0] == ">":
data = data.byteswap()
ext.append(self, data, axis)
return self.shape
def delete(self, delete_len, axis=0, start=None):
"""Delete :paramref:`delete_len` positions along the :paramref:`axis` from the
:paramref:`start`.
Parameters
----------
delete_len: int
The number of elements to delete in the `array.shape[axis]`.
axis: int, optional
The axis that will be shrunk.
Default is 0.
start: int, None, optional
The starting point for deleting the elements. If None (default)
the deleted elements will be at the end of the array.
Notes
-----
If :paramref:`delete_len` is not a multiple of `chunks[axis]`,
:paramref:`start` must be either None or `shape[axis] - delete_len` (which are equivalent).
Otherwise, :paramref:`start` must also be a multiple of `chunks[axis]`.
For example, letâs suppose that we have an array with `shape = [20, 20]` and `chunks = [7, 7]`.
If `delete_len = 5` and `axis = 0`, because :paramref:`delete_len`
is not a multiple of `chunks[axis]`, :paramref:`start`
must be `None` or `shape[axis] - delete_len = 15`. In both cases, the deleted elements
will be the same (those at the end) and the new shape will be `[15, 20]`.
If we would like to delete some elements
in the middle of the array, :paramref:`start` and :paramref:`delete_len` both must be a multiple
of `chunks[axis]`. So the only possibilities is this particular case would be
`start = 0` and `delete_len = 7` or `delete_len = 14` which would give an array with
shape `[13, 20]` or `[6, 20]`. Or `start = 7` and
`delete_len = 7` which would give an array with shape `[13, 20]`.
See Also
--------
resize
insert
append
"""
ext.delete(self, axis=axis, start=start, delete_len=delete_len)
return self.shape
def iter_read_block(self, iterblock: tuple = None):
if iterblock is None:
if self.chunks is not None:
iterblock = self.chunks
else:
iterblock, _ = ia.partition_advice(self.shape)
return ext.ReadBlockIter(self, iterblock)
def iter_write_block(self, iterblock=None):
if iterblock is None:
if self.chunks:
iterblock = self.chunks
else:
iterblock, _ = ia.partition_advice(self.shape)
return ext.WriteBlockIter(self, iterblock)
def __getitem__(
self, key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], IArray], /
) -> IArray:
if key == () and self.ndim == 0:
return self.data[()]
if isinstance(key, ia.LazyExpr):
return key.update_expr(new_op=(self, f"[]", key))
# Massage the key a bit so that it is compatible with self.shape
key, mask = process_key(key, self.shape)
start = [sl.start for sl in key]
stop = [sl.stop for sl in key]
return super().__getitem__([start, stop, mask])
def __setitem__(
self,
key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], IArray],
value: Union[int, float, bool, IArray],
/,
) -> None:
key, mask = process_key(key, self.shape)
start = [sl.start for sl in key]
stop = [sl.stop for sl in key]
shape = [sp - st for sp, st in zip(stop, start)]
if isinstance(value, (float, int, bool)):
value = np.full(shape, value, dtype=self.dtype)
elif isinstance(value, ia.IArray):
if self.np_dtype is None:
value = value.data
else:
value = value.data
value = value.astype(dtype=self.dtype)
elif self.np_dtype is not None:
value = np.full(shape, value, dtype=self.dtype)
with ia.config(cfg=self.cfg) as cfg:
ext.set_slice(cfg, self, start, stop, value)
def set_orthogonal_selection(self, selection, value):
"""Modify data via a selection for each dimension of the array.
Parameters
----------
selection: int, slice or integer array.
The selection for each dimension of the array.
value: value or `np.ndarray `_.
Value to be stored into the array.
Returns
-------
None
Notes
-----
This function can also be replaced by `self.oindex[selection]`.
See Also
--------
get_orthogonal_selection
"""
selection = process_selection(selection, self.shape)
if type(value) == list:
value = np.array(value)
elif isinstance(value, (int, float)):
shape = [len(s) for s in selection]
value = np.full(shape, value, self.dtype)
with ia.config(cfg=self.cfg) as cfg:
return ext.set_orthogonal_selection(cfg, self, selection, value)
def get_orthogonal_selection(self, selection):
"""Retrieve data by making a selection for each dimension of the array.
Parameters
----------
selection: list
The selection for each dimension. It can be either
an integer (indexing a single item), a slice or an array of integers.
Returns
-------
out: `np.ndarray `_
Notes
-----
This function can also be replaced by `self.oindex[selection]`.
See Also
--------
set_orthogonal_selection
"""
selection = process_selection(selection, self.shape)
shape = tuple(len(s) for s in selection)
with ia.config(cfg=self.cfg) as cfg:
dst = np.ones(shape, dtype=self.dtype)
return ext.get_orthogonal_selection(cfg, self, dst, selection)
def __iter__(self):
return self.iter_read_block()
def __str__(self):
return f""
def __repr__(self):
return str(self)
def __matmul__(self, value: IArray, /) -> IArray:
self._check_allowed_dtypes(value, "numeric", "__matmul__")
a = self
return ia.matmul(a, value)
def __rmatmul__(self, value: IArray, /) -> IArray:
self._check_allowed_dtypes(value, "numeric", "__rmatmul__")
a = self
return ia.matmul(value, a)
def __add__(self, value: Union[int, float, IArray], /):
self._check_allowed_dtypes(value, "numeric", "__add__")
return ia.LazyExpr(new_op=(self, "+", value))
def __radd__(self, value: Union[int, float, IArray], /):
self._check_allowed_dtypes(value, "numeric", "__radd__")
return ia.LazyExpr(new_op=(value, "+", self))
def __iadd__(self, value: Union[int, float, IArray], /):
raise NotImplementedError("self.__iadd__ is not supported yet")
def __sub__(self, value: Union[int, float, IArray], /):
self._check_allowed_dtypes(value, "numeric", "__sub__")
return ia.LazyExpr(new_op=(self, "-", value))
def __rsub__(self, value: Union[int, float, IArray], /):
self._check_allowed_dtypes(value, "numeric", "__rsub__")
return ia.LazyExpr(new_op=(value, "-", self))
def __isub__(self, value: Union[int, float, IArray], /):
raise NotImplementedError("self.__isub__ is not supported yet")
def __array_namespace__(self, *, api_version: Optional[str] = None) -> Any:
if api_version is not None and not api_version.startswith("2021."):
raise ValueError(f"Unrecognized array API version: {api_version!r}")
return ia
def __mul__(self, value: Union[int, float, IArray], /):
self._check_allowed_dtypes(value, "numeric", "__mul__")
return ia.LazyExpr(new_op=(self, "*", value))
def __rmul__(self, value: Union[int, float, IArray], /):
self._check_allowed_dtypes(value, "numeric", "__rmul__")
return ia.LazyExpr(new_op=(value, "*", self))
def __imul__(self, value: Union[int, float, IArray], /):
raise NotImplementedError("self.__imul__ is not supported yet")
def __truediv__(self, value: Union[int, float, IArray], /):
self._check_allowed_dtypes(value, "numeric", "__truediv__")
return ia.LazyExpr(new_op=(self, "/", value))
def __rtruediv__(self, value: Union[int, float, IArray], /):
self._check_allowed_dtypes(value, "numeric", "__rtruediv__")
return ia.LazyExpr(new_op=(value, "/", self))
def __itruediv__(self, value: Union[int, float, IArray], /):
raise NotImplementedError("self.__itruediv__ is not supported yet")
def __lt__(self, value: Union[int, float, IArray], /):
self._check_allowed_dtypes(value, "numeric", "__lt__")
return ia.LazyExpr(new_op=(self, "<", value))
def __le__(self, value: Union[int, float, IArray], /):
self._check_allowed_dtypes(value, "numeric", "__le__")
return ia.LazyExpr(new_op=(self, "<=", value))
def __gt__(self, value: Union[int, float, IArray], /):
self._check_allowed_dtypes(value, "numeric", "__gt__")
return ia.LazyExpr(new_op=(self, ">", value))
def __ge__(self, value: Union[int, float, IArray], /):
self._check_allowed_dtypes(value, "numeric", "__ge__")
return ia.LazyExpr(new_op=(self, ">=", value))
def __eq__(self, value: Union[int, float, bool, IArray], /):
self._check_allowed_dtypes(value, "all", "__eq__")
if ia._disable_overloaded_equal:
return self is value
return ia.LazyExpr(new_op=(self, "==", value))
def __ne__(self, value: Union[int, float, bool, IArray], /):
self._check_allowed_dtypes(value, "all", "__ne__")
return ia.LazyExpr(new_op=(self, "!=", value))
def __pos__(self) -> IArray:
if self.dtype not in _numeric_dtypes:
raise TypeError("Only numeric dtypes are allowed in __pos__")
return self.copy()
# def __array_function__(self, func, types, args, kwargs):
# if not all(issubclass(t, np.ndarray) for t in types):
# # Defer to any non-subclasses that implement __array_function__
# return NotImplemented
#
# # Use NumPy's private implementation without __array_function__
# # dispatching
# return func._implementation(*args, **kwargs)
# def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
# print("method:", method)
@property
def T(self):
"""
Transpose of the array.
See :meth:`transpose`.
"""
return self.transpose()
def transpose(self, **kwargs):
"""Transpose the array.
Parameters
----------
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config`
dataclass that should override the current configuration.
Returns
-------
:ref:`IArray`
The transposed array.
"""
return ia.matrix_transpose(self, **kwargs)
def __abs__(self):
"""
Absolute value, element-wise.
See :func:`abs`.
"""
if self.dtype not in _numeric_dtypes:
raise TypeError("Only numeric dtypes are allowed in __abs__")
return ia.LazyExpr(new_op=(self, "abs", None))
def __neg__(self):
"""
Numerical negative, element-wise.
See :func:`negative`.
"""
if self.dtype not in _numeric_dtypes:
raise TypeError("Only numeric dtypes are allowed in __neg__")
return ia.LazyExpr(new_op=(self, "negate", None))
def __pow__(self, iarr2: Union[int, float, IArray], /):
"""
First array elements raised to powers from second array, element-wise.
See :func:`pow`.
"""
self._check_allowed_dtypes(iarr2, "numeric", "__pow__")
return ia.LazyExpr(new_op=(self, "pow", iarr2))
def __rpow__(self, iarr2: Union[int, float, IArray], /):
self._check_allowed_dtypes(iarr2, "numeric", "__pow__")
return ia.LazyExpr(new_op=(iarr2, "pow", self))
@attrs.setter
def attrs(self, value):
self._attrs = value
# Not supported methods
def __and__(self, value: Union[int, bool, IArray], /) -> IArray:
raise NotImplementedError("self.__and__ is not supported yet")
def __rand__(self, value: Union[int, bool, IArray], /) -> IArray:
raise NotImplementedError("self.__rand__ is not supported yet")
def __iand__(self, value: Union[int, bool, IArray], /):
raise NotImplementedError("self.__iand__ is not supported yet")
def __bool__(self) -> bool:
if self.ndim != 0:
raise AttributeError(
"Cannot convert a non zero dimensional array into a Python scalar"
)
return bool(self.data)
def __dlpack__(self, *, stream: Optional[Union[int, Any]] = None) -> PyCapsule:
raise NotImplementedError("DLPack is not supported yet")
def __dlpack_device__(self: IArray, /) -> Tuple[IntEnum, int]:
raise NotImplementedError("DLPack is not supported yet")
def __float__(self) -> float:
if self.ndim != 0:
raise AttributeError(
"Cannot convert a non zero dimensional array into a Python scalar"
)
return float(self.data)
def __floordiv__(self, value: Union[int, float, IArray], /) -> IArray:
raise NotImplementedError("self.__floordiv__ is not supported yet")
def __rfloordiv__(self, value: Union[int, float, IArray], /) -> IArray:
raise NotImplementedError("self.__rfloordiv__ is not supported yet")
def __ifloordiv__(self, value: Union[int, float, IArray], /):
raise NotImplementedError("self.__ifloordiv__ is not supported yet")
def __index__(self) -> int:
return self.__int__()
def __int__(self) -> int:
if self.ndim != 0:
raise AttributeError(
"Cannot convert a non zero dimensional array into a Python scalar"
)
return int(self.data)
def __invert__(self) -> IArray:
raise NotImplementedError("self.__invert__ is not supported yet")
def __lshift__(self, value: Union[int, IArray], /) -> IArray:
raise NotImplementedError("self.__lshift__ is not supported yet")
def __rlshift__(self, value: Union[int, IArray], /) -> IArray:
raise NotImplementedError("self.__rlshift__ is not supported yet")
def __ilshift__(self, value: Union[int, IArray], /):
raise NotImplementedError("self.__ilshift__ is not supported yet")
def __mod__(self, value: Union[int, float, IArray], /) -> IArray:
raise NotImplementedError("self.__mod__ is not supported yet")
def __rmod__(self, value: Union[int, float, IArray], /) -> IArray:
raise NotImplementedError("self.__rmod__ is not supported yet")
def __imod__(self, value: Union[int, float, IArray], /):
raise NotImplementedError("self.__imod__ is not supported yet")
def __or__(self, value: Union[int, bool, IArray], /) -> IArray:
raise NotImplementedError("self.__or__ is not supported yet")
def __ror__(self, value: Union[int, bool, IArray], /) -> IArray:
raise NotImplementedError("self.__ror__ is not supported yet")
def __ior__(self, value: Union[int, bool, IArray], /):
raise NotImplementedError("self.__ior__ is not supported yet")
def __rshift__(self, value: Union[int, IArray], /) -> IArray:
raise NotImplementedError("self.__rshift__ is not supported yet")
def __rrshift__(self, value: Union[int, IArray], /) -> IArray:
raise NotImplementedError("self.__rrshift__ is not supported yet")
def __irshift__(self, value: Union[int, IArray], /):
raise NotImplementedError("self.__irshift__ is not supported yet")
def __xor__(self, value: Union[int, bool, IArray], /) -> IArray:
raise NotImplementedError("self.__xor__ is not supported yet")
def __rxor__(self, value: Union[int, bool, IArray], /) -> IArray:
raise NotImplementedError("self.__rxor__ is not supported yet")
def __ixor__(self, value: Union[int, bool, IArray], /):
raise NotImplementedError("self.__ixor__ is not supported yet")
def to_device(self, device: device, /, *, stream: Optional[Union[int, Any]] = None) -> IArray:
raise NotImplementedError("self.to_device is not supported yet")
def astype(x: IArray, view_dtype, /, *, copy: bool = False) -> IArray:
"""
Cast the array into a view of a specified type.
Parameters
----------
x: :ref:`IArray`
The array to cast.
view_dtype: (float64, float32, int64, int32, int16, int8, uint64, uint32, uint16,
uint8, bool)
The dtype in which the array will be casted. Only upcasting is supported
unless :paramref:`copy` is `True`.
copy: bool
Whether to copy the array or do a view instead. Default is False.
Returns
-------
:ref:`IArray`
The new view or array as a normal :ref:`IArray`.
"""
if copy:
return x.copy(dtype=view_dtype)
view_dtypesize = np.dtype(view_dtype).itemsize
src_dtypesize = np.dtype(x.dtype).itemsize
if view_dtypesize < src_dtypesize:
raise OverflowError("`view_dtype` itemsize must be greater or equal than `self.dtype`")
return ext.get_type_view(x.cfg, x, view_dtype)
def abs(iarr: IArray, /):
"""
Absolute value, element-wise.
Parameters
----------
iarr: :ref:`IArray`
Input array. Should have a numeric data type.
Returns
-------
abs: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
absolute value of each element in :paramref:`iarr`.
References
----------
`np.absolute `_
"""
return iarr.__abs__()
def acos(iarr: IArray, /):
"""
Trigonometric inverse cosine, element-wise.
The inverse of :py:obj:`cos` so that, if :math:`y = \\cos(x)`, then :math:`x = \\arccos(y)`.
Parameters
----------
iarr: :ref:`IArray`
x-coordinate on the unit circle. For real arguments, the domain is :math:`\\left [ -1, 1 \\right]`.
Should have a floating-point data type.
Returns
-------
angle: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
angle of the ray intersecting the unit circle at the given x-coordinate in radians
:math:`[0, \\pi]`.
Notes
-----
:py:obj:`acos` is a multivalued function: for each :math:`x` there are infinitely many numbers :math:`z`
such that :math:`\\cos(z) = x`. The convention is to return the angle :math:`z` whose real part lies in
:math:`\\left [ 0, \\pi \\right]`.
References
----------
`np.acos `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in acos")
return ia.LazyExpr(new_op=(iarr, "acos", None))
def add(iarr1: IArray, iarr2: IArray, /):
"""
Add arguments element-wise.
Parameters
----------
iarr1: :ref:`IArray`
First input array. Should have a numeric data type.
iarr2: :ref:`IArray`
Second input array. Should have a numeric data type.
Returns
-------
add: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute
the sum of :paramref:`iarr1` and :paramref:`iarr2`, element-wise.
"""
return iarr1 + iarr2
def asin(iarr: IArray, /):
"""
Trigonometric inverse sine, element-wise.
The inverse of :py:obj:`sin` so that, if :math:`y = \\sin(x)`, then :math:`x = \\arcsin(y)`.
Parameters
----------
iarr: :ref:`IArray`
y-coordinate on the unit circle. Should have a floating-point data type.
Returns
-------
angle: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the inverse
sine of each element in :math:`x`, in radians and in the closed interval
:math:`\\left[-\\frac{\\pi}{2}, \\frac{\\pi}{2}\\right]`.
Notes
-----
:py:obj:`asin` is a multivalued function: for each :math:`x` there are infinitely many numbers :math:`z`
such that :math:`\\sin(z) = x`. The convention is to return the angle :math:`z` whose real part lies in
:math:`\\left[-\\frac{\\pi}{2}, \\frac{\\pi}{2}\\right]`.
References
----------
`np.asin `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in asin")
return ia.LazyExpr(new_op=(iarr, "asin", None))
def atan(iarr: IArray, /):
"""
Trigonometric inverse tangent, element-wise.
The inverse of :py:obj:`tan` so that, if :math:`y = \\tan(x)`, then :math:`x = \\arctan(y)`.
Parameters
----------
iarr: :ref:`IArray`
Input array. Should have a floating-point data type.
Returns
-------
angle: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
angles in radians, in the range :math:`\\left[-\\frac{\\pi}{2}, \\frac{\\pi}{2}\\right]`.
Notes
-----
:py:obj:`atan` is a multi-valued function: for each x there are infinitely many numbers :math:`z`
such that :math:`\\tan(z) = x`. The convention is to return the angle :math:`z` whose real part lies in
:math:`\\left[-\\frac{\\pi}{2}, \\frac{\\pi}{2}\\right]`.
References
----------
`np.atan `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in atan")
return ia.LazyExpr(new_op=(iarr, "atan", None))
def atan2(iarr1: IArray, iarr2: IArray, /):
"""
Element-wise arc tangent of :math:`\\frac{iarr_1}{iarr_2}` choosing the quadrant correctly.
Parameters
----------
iarr1: :ref:`IArray`
y-coordinates. Should have a floating-point data type.
iarr2: :ref:`IArray`
x-coordinates. Should have a floating-point data type.
Returns
-------
angle: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
angles in radians, in the range :math:`[-\\pi, \\pi]`.
References
----------
`np.atan2 `_
"""
iarr1._check_allowed_dtypes(iarr2, "floating-point", "atan2")
return ia.LazyExpr(new_op=(iarr1, "atan2", iarr2))
def ceil(iarr: IArray, /):
"""
Return the ceiling of the input, element-wise. It is often denoted as :math:`\\lceil x \\rceil`.
Parameters
----------
iarr: :ref:`IArray`
Input array. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
ceiling of each element in :math:`x`.
References
----------
`np.ceil `_
"""
if iarr.dtype not in _numeric_dtypes:
raise TypeError("Only numeric dtypes are allowed in ceil")
return ia.LazyExpr(new_op=(iarr, "ceil", None))
def cos(iarr: IArray, /):
"""
Trigonometric cosine, element-wise.
Parameters
----------
iarr: :ref:`IArray`
Angle, in radians. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the actual
cosine values.
References
----------
`np.cos `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in cos")
return ia.LazyExpr(new_op=(iarr, "cos", None))
def cosh(iarr: IArray, /):
"""
Hyperbolic cosine, element-wise.
Equivalent to ``1/2 * (ia.exp(x) + ia.exp(-x))``.
Parameters
----------
iarr: :ref:`IArray`
Input data. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the actual
hyperbolic cosine values.
References
----------
`np.cosh `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in cosh")
return ia.LazyExpr(new_op=(iarr, "cosh", None))
def divide(iarr1: IArray, iarr2: IArray, /):
"""
Divide arrays element-wise.
Parameters
----------
iarr1: :ref:`IArray`
Dividend array. Should have a numeric data type.
iarr2: :ref:`IArray`
Divisor array. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the quotient
`iarr1 / iarr2` element-wise.
"""
return iarr1 / iarr2
def equal(iarr1: IArray, iarr2: IArray, /):
"""
Return (:paramref:`iarr1` == :paramref:`iarr2`) element-wise.
Parameters
----------
iarr1: :ref:`IArray`
First input array.
iarr2: :ref:`IArray`
Second input array.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the comparison
of :paramref:`iarr1` and :paramref:`iarr2` element-wise.
"""
return iarr1 == iarr2
def exp(iarr: IArray, /):
"""
Calculate the exponential of all elements in the input array.
Parameters
----------
iarr: :ref:`IArray`
Input array. Should have a floating-point data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
element-wise exponential of input data.
References
----------
`np.exp `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in exp")
return ia.LazyExpr(new_op=(iarr, "exp", None))
def expm1(iarr: IArray, /):
"""
Calculate :math:`\\exp(x) - 1` for all elements in the input array.
Parameters
----------
iarr: :ref:`IArray`
Input array. Should have a floating-point data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
element-wise exponential minus one.
References
----------
`np.expm1 `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in exp")
return eval("ia.exp(x) - 1", {"ia": ia, "x": iarr})
def floor(iarr: IArray, /):
"""
Return the floor of the input, element-wise. It is often denoted as :math:`\\lfloor x \\rfloor`.
Parameters
----------
iarr: :ref:`IArray`
Input array. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
floor of each element in input data.
References
----------
`np.floor `_
"""
if iarr.dtype not in _numeric_dtypes:
raise TypeError("Only numeric dtypes are allowed in floor")
return ia.LazyExpr(new_op=(iarr, "floor", None))
def greater(iarr1: IArray, iarr2: IArray, /):
"""
Return the truth value of (:paramref:`iarr1` > :paramref:`iarr2`) element-wise.
Parameters
----------
iarr1: :ref:`IArray`
First input array. Should have a numeric data type.
iarr2: :ref:`IArray`
Second input array. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
actual comparison element-wise.
"""
return iarr1 > iarr2
def greater_equal(iarr1: IArray, iarr2: IArray, /):
"""
Return the truth value of (:paramref:`iarr1` >= :paramref:`iarr2`) element-wise.
Parameters
----------
iarr1: :ref:`IArray`
First input array. Should have a numeric data type.
iarr2: :ref:`IArray`
Second input array. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
actual comparison element-wise.
"""
return iarr1 >= iarr2
def less(iarr1: IArray, iarr2: IArray, /):
"""
Return the truth value of (:paramref:`iarr1` < :paramref:`iarr2`) element-wise.
Parameters
----------
iarr1: :ref:`IArray`
First input array. Should have a numeric data type
iarr2: :ref:`IArray`
Second input array. Should have a numeric data type
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
actual comparison element-wise.
"""
return iarr1 < iarr2
def less_equal(iarr1: IArray, iarr2: IArray, /):
"""
Return the truth value of (:paramref:`iarr1` <= :paramref:`iarr2`) element-wise.
Parameters
----------
iarr1: :ref:`IArray`
First input array. Should have a numeric data type.
iarr2: :ref:`IArray`
Second input array. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
actual comparison element-wise.
"""
return iarr1 <= iarr2
def log(iarr: IArray, /):
"""
Natural logarithm, element-wise.
The natural logarithm log is the inverse of the exponential function, so that
:math:`\\log(\\exp(x)) = x`. The natural logarithm is logarithm in base :math:`e`.
Parameters
----------
iarr: :ref:`IArray`
Input array. Should have a floating-point data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
natural logarithm of input data, element-wise.
References
----------
`np.log `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in log")
return ia.LazyExpr(new_op=(iarr, "log", None))
def log1p(iarr: IArray, /):
"""
Natural logarithm of one plus the input array, element-wise.
The natural logarithm log is the inverse of the exponential function, so that
:math:`\\log(\\exp(x+1)) = x + 1`. The natural logarithm is logarithm in base :math:`e`.
Parameters
----------
iarr: :ref:`IArray`
Input array. Should have a floating-point data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
natural logarithm of one plus the input data, element-wise.
References
----------
`np.log1p `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in log")
return ia.expr_from_string("log(x + 1)", {"x": iarr})
def log10(iarr: IArray, /):
"""
Return the base 10 logarithm of the input array, element-wise.
Parameters
----------
iarr: :ref:`IArray`
Input array. Should have a floating-point data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
logarithm to the base 10 of input data, element-wise.
References
----------
`np.log10 `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in log10")
return ia.LazyExpr(new_op=(iarr, "log10", None))
def logaddexp(iarr1: IArray, iarr2: IArray, /):
"""
Logarithm of the sum of exponentiations of the inputs.
Calculates :math:`\\log(\\exp(iarr1) + \\exp(iarr2))`.
Parameters
----------
iarr1: :ref:`IArray`
First input array. Should have a floating-point data type.
iarr2: :ref:`IArray`
Second input array. Should have a floating-point data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
logarithm to the base 10 of `exp(iarr1) + exp(iarr2)`, element-wise.
References
----------
`np.logaddexp `_
"""
if iarr1.dtype not in _floating_dtypes or iarr2.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in log")
return ia.expr_from_string("log(exp(x) + exp(y))", {"x": iarr1, "y": iarr2})
def multiply(iarr1: IArray, iarr2: IArray, /):
"""
Multiply arguments element-wise.
Parameters
----------
iarr1: :ref:`IArray`
First input array. Should have a numeric data type.
iarr2: :ref:`IArray`
Second input array. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
product of :paramref:`iarr1` and :paramref:`iarr2`, element-wise.
References
----------
`np.multiply `_
"""
if iarr1.dtype not in _numeric_dtypes or iarr2.dtype not in _numeric_dtypes:
raise TypeError("Only numeric dtypes are allowed in log")
return iarr1 * iarr2
def negative(iarr: IArray, /):
"""
Numerical negative, element-wise.
Parameters
----------
iarr: :ref:`IArray`
Input array. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute
:math:`out = -iarr`.
References
----------
`np.negative `_
"""
return iarr.__neg__()
def not_equal(iarr: IArray, iarr2: IArray, /):
"""
Return the truth value of (:paramref:`iarr1` != :paramref:`iarr2`) element-wise.
Parameters
----------
iarr1: :ref:`IArray`
First input array. Should have a numeric data type.
iarr2: :ref:`IArray`
Second input array. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
actual comparison element-wise.
"""
return iarr.__ne__(iarr2)
def positive(x: IArray, /):
"""
Numerical positive element-wise.
Parameters
----------
x: :ref:`IArray`
Input array. Should have a numeric data type.
Returns
-------
out: :ref:`IArray`
An array containing the evaluated result for each element in :paramref:`x`.
Notes
-----
Equivalent to :meth:`IArray.copy` but only for numerical dtypes.
"""
return x.__pos__()
def pow(iarr1: IArray, iarr2: Union[int, float, IArray], /):
"""
First array elements raised to powers from second array, element-wise.
Parameters
----------
iarr1: :ref:`IArray`
The bases. Should have a numeric data type.
iarr2: int, float or :ref:`IArray`
The exponents. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
bases raised to the exponents.
References
----------
`np.power `_
"""
return iarr1.__pow__(iarr2)
def sin(iarr: IArray, /):
"""
Trigonometric sine, element-wise.
Parameters
----------
iarr: :ref:`IArray`
Angle, in radians. Should have a floating-point data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the actual
sine values.
References
----------
`np.sin `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in sin")
return ia.LazyExpr(new_op=(iarr, "sin", None))
def sinh(iarr: IArray, /):
"""
Hyperbolic sine, element-wise.
Equivalent to ``1/2 * (ia.exp(x) - ia.exp(-x))``.
Parameters
----------
iarr: :ref:`IArray`
Input data. Should have a floating-point data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the actual
hyperbolic sine values.
References
----------
`np.sinh `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in sinh")
return ia.LazyExpr(new_op=(iarr, "sinh", None))
def square(iarr1: IArray, /):
"""
Return the element-wise square of the input.
Parameters
----------
iarr: :ref:`IArray`
Input array. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute :math:`x * x` element-wise.
References
----------
`np.square `_
"""
return iarr1 * iarr1
def sqrt(iarr: IArray, /):
"""
Return the non-negative square-root of an array, element-wise.
Parameters
----------
iarr: :ref:`IArray`
The values whose square-roots are required. Should have a floating-point data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the actual
positive square-root of each element in input data.
References
----------
`np.sqrt `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in sqrt")
return ia.LazyExpr(new_op=(iarr, "sqrt", None))
def subtract(iarr1: IArray, iarr2: IArray, /):
"""
Subtract arguments, element-wise.
Parameters
----------
iarr1: :ref:`IArray`
Minuend array. Should have a numeric data type.
iarr2: :ref:`IArray`
Subtrahend array. Should have a numeric data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the
difference of :paramref:`iarr1` and paramref:`iarr2`, element-wise.
"""
return iarr1 - iarr2
def tan(iarr: IArray, /):
"""
Compute tangent element-wise.
Equivalent to ``ia.sin(x)/ia.cos(x)`` element-wise.
Parameters
----------
iarr: :ref:`IArray`
Input data. Should have a floating-point data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the actual
tangent values.
References
----------
`np.tan `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in tan")
return ia.LazyExpr(new_op=(iarr, "tan", None))
def tanh(iarr: IArray, /):
"""
Compute hyperbolic tangent element-wise.
Equivalent to ``ia.sinh(x)/ia.cosh(x)``.
Parameters
----------
iarr: :ref:`IArray`
Input data. Should have a floating-point data type.
Returns
-------
out: :ref:`iarray.Expr`
A lazy expression that must be evaluated via `out.eval()`, which will compute the actual
hyperbolic tangent values.
References
----------
`np.tanh `_
"""
if iarr.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in tanh")
return ia.LazyExpr(new_op=(iarr, "tanh", None))
# Reductions
def reduce(
a: IArray,
method: ia.Reduce,
axis: Union[int, tuple] = None,
oneshot=True,
correction: Union[int, float] = 0.0,
cfg: ia.Config = None,
**kwargs,
):
if axis is None:
axis = range(a.ndim)[::-1]
if isinstance(axis, int):
axis = (axis,)
shape = tuple([s for i, s in enumerate(a.shape) if i not in axis])
if cfg is None:
cfg = ia.get_config_defaults()
dtype = kwargs.get("dtype")
with ia.config(shape=shape, cfg=cfg, **kwargs) as cfg:
c = ext.reduce_multi(cfg, a, method, axis, oneshot, correction)
if dtype is not None and dtype != c.dtype:
raise RuntimeError("Cannot set the result's data type")
return c
def all(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
keepdims: bool = False,
cfg: ia.Config = None,
**kwargs,
):
"""
Tests whether all input array elements evaluate to True along a specified axis.
Parameters
----------
a : :ref:`IArray`
Input data.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
keepdims : bool
Whether to keep the reduced axes in the result or not. The only supported value for this param is
`False` (the default).
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
all : :ref:`IArray`
The result is an array of dimension a.ndim - len(axis) with boolean dtype.
"""
if keepdims:
raise NotImplementedError("Keeping the original array dimensions is not supported yet")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.ALL, axis, oneshot=True, cfg=cfg, **kwargs)
def any(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
keepdims: bool = False,
cfg: ia.Config = None,
**kwargs,
):
"""
Tests whether any input array element evaluates to `True` along a specified axis.
Parameters
----------
a : :ref:`IArray`
Input data.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
keepdims : bool
Whether to keep the reduced axes in the result or not. The only supported value for this param is
`False` (the default).
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
any : :ref:`IArray`
The result is an array of dimension a.ndim - len(axis) with boolean dtype.
"""
if keepdims:
raise NotImplementedError("Keeping the original array dimensions is not supported yet")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.ANY, axis, oneshot=True, cfg=cfg, **kwargs)
def max(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
keepdims: bool = False,
oneshot: bool = False,
cfg: ia.Config = None,
**kwargs,
):
"""
Return the maximum of an array or maximum along an axis.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a numeric data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
keepdims : bool
Whether to keep the reduced axes in the result or not. The only supported value for this param is
`False` (the default).
oneshot : bool
Enforce the use of the oneshot algorithm. Oneshot normally uses less memory,
albeit is slower in general. Default is False.
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
max : :ref:`IArray`
Maximum of a. The result is
an array of dimension a.ndim - len(axis). The `dtype` is always the `dtype` of :paramref:`a`.
"""
if keepdims:
raise NotImplementedError("Keeping the original array dimensions is not supported yet")
if a.dtype not in _numeric_dtypes:
raise TypeError("Only numeric dtypes are allowed in max; use `ia.any` instead")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.MAX, axis, oneshot=oneshot, cfg=cfg, **kwargs)
def min(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
keepdims: bool = False,
oneshot: bool = False,
cfg: ia.Config = None,
**kwargs,
):
"""
Return the minimum of an array or minimum along an axis.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a numeric data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
keepdims : bool
Whether to keep the reduced axes in the result or not. The only supported value for this param is
`False` (the default).
oneshot : bool
Enforce the use of the oneshot algorithm. Oneshot normally uses less memory,
albeit is slower in general. Default is False.
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
min : :ref:`IArray`
Minimum of a. The result is
an array of dimension a.ndim - len(axis). The `dtype` is always the `dtype` of :paramref:`a`.
"""
if keepdims:
raise NotImplementedError("Keeping the original array dimensions is not supported yet")
if a.dtype not in _numeric_dtypes:
raise TypeError("Only numeric dtypes are allowed in min; use `ia.all` instead")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.MIN, axis, oneshot=oneshot, cfg=cfg, **kwargs)
def sum(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
keepdims: bool = False,
oneshot: bool = False,
cfg: ia.Config = None,
**kwargs,
):
"""
Return the sum of array elements over a given axis.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a numeric data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
keepdims : bool
Whether to keep the reduced axes in the result or not. The only supported value for this param is
`False` (the default).
oneshot : bool
Enforce the use of the oneshot algorithm. Oneshot normally uses less memory,
albeit is slower in general. Default is False.
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
sum : :ref:`IArray`
Sum of a. The result is
an array of dimension a.ndim - len(axis). Its `dtype` is `np.int64` for integers and bools,
`np.uint64` for unsigned integers and the `dtype` of :paramref:`a` otherwise.
"""
if keepdims:
raise NotImplementedError("Keeping the original array dimensions is not supported yet")
if a.dtype not in _numeric_dtypes:
raise TypeError("Only numeric dtypes are allowed in sum")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.SUM, axis, oneshot=oneshot, cfg=cfg, **kwargs)
def prod(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
keepdims: bool = False,
oneshot: bool = False,
cfg: ia.Config = None,
**kwargs,
):
"""
Return the product of array elements over a given axis.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a numeric data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
keepdims : bool
Whether to keep the reduced axes in the result or not. The only supported value for this param is
`False` (the default).
oneshot : bool
Enforce the use of the oneshot algorithm. Oneshot normally uses less memory,
albeit is slower in general. Default is False.
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
prod : :ref:`IArray`
Product of a. The result is
an array of dimension a.ndim - len(axis). Its `dtype` is `np.int64` for integers and bools,
`np.uint64` for unsigned integers and the `dtype` of :paramref:`a` otherwise.
"""
if keepdims:
raise NotImplementedError("Keeping the original array dimensions is not supported yet")
if a.dtype not in _numeric_dtypes:
raise TypeError("Only numeric dtypes are allowed in prod")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.PROD, axis, oneshot=oneshot, cfg=cfg, **kwargs)
def mean(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
keepdims: bool = False,
oneshot: bool = False,
cfg: ia.Config = None,
**kwargs,
):
"""
Compute the arithmetic mean along the specified axis. Returns the average of the array elements.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a floating-point data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
oneshot : bool
Enforce the use of the oneshot algorithm. Oneshot normally uses less memory,
albeit is slower in general. Default is False.
keepdims : bool
Whether to keep the reduced axes in the result or not. The only supported value for this param is
`False` (the default).
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
mean : :ref:`IArray`
Mean of a. The result is
an array of dimension a.ndim - len(axis). Its `dtype` is `np.float32` when the `dtype` of
:paramref:`a` is `np.float32` and `np.float64` otherwise.
"""
if keepdims:
raise NotImplementedError("Keeping the original array dimensions is not supported yet")
if a.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in mean")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.MEAN, axis, oneshot=oneshot, cfg=cfg, **kwargs)
def std(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
keepdims: bool = False,
correction: Union[int, float] = 0.0,
cfg: ia.Config = None,
**kwargs,
):
"""
Returns the standard deviation, a measure of the spread of a distribution,
of the array elements. The standard deviation is computed for the flattened
array by default, otherwise over the specified axis.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a floating-point data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
correction : int or float
Degrees of freedom adjustment. Default is 0.
keepdims : bool
Whether to keep the reduced axes in the result or not. The only supported value for this param is
`False` (the default).
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
std : :ref:`IArray`
Standard deviation of a. The result is
an array of dimension a.ndim - len(axis). Its `dtype` is `np.float32` when the `dtype` of
:paramref:`a` is `np.float32` and `np.float64` otherwise.
"""
if keepdims:
raise NotImplementedError("Keeping the original array dimensions is not supported yet")
if a.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in std")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(
a, ia.Reduce.STD, axis, oneshot=True, correction=correction, cfg=cfg, **kwargs
)
def var(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
correction: Union[int, float] = 0.0,
keepdims: bool = False,
cfg: ia.Config = None,
**kwargs,
):
"""
Compute the variance along the specified axis. Returns the variance of the array elements,
a measure of the spread of a distribution. The variance is computed for the flattened
array by default, otherwise over the specified axis.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a floating-point data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
correction : int or float
Degrees of freedom adjustment. Default is 0.
keepdims : bool
Whether to keep the reduced axes in the result or not. The only supported value for this param is
`False` (the default).
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
var : :ref:`IArray`
Variance of a. The result is
an array of dimension a.ndim - len(axis). Its `dtype` is `np.float32` when the `dtype` of
:paramref:`a` is `np.float32` and `np.float64` otherwise.
"""
if keepdims:
raise NotImplementedError("Keeping the original array dimensions is not supported yet")
if a.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in var")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(
a, ia.Reduce.VAR, axis, oneshot=True, correction=correction, cfg=cfg, **kwargs
)
def median(a: IArray, /, *, axis: Union[int, tuple] = None, cfg: ia.Config = None, **kwargs):
"""
Compute the median along the specified axis. Returns the median of the array elements.
Parameters
----------
a : :ref:`IArray`
Input data.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
median : :ref:`IArray`
Median of a. The result is
an array of dimension a.ndim - len(axis). Its `dtype` is `np.float32` when the `dtype` of
:paramref:`a` is `np.float32` and `np.float64` otherwise.
"""
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.MEDIAN, axis, oneshot=True, cfg=cfg, **kwargs)
def nanmax(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
oneshot: bool = False,
cfg: ia.Config = None,
**kwargs,
):
"""
Return the maximum of an array or maximum along an axis ignoring NaNs.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a floating-point data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
oneshot : bool
Enforce the use of the oneshot algorithm. Oneshot normally uses less memory,
albeit is slower in general. Default is False.
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
max : :ref:`IArray`
Maximum of a. The result is
an array of dimension a.ndim - len(axis). The `dtype` is always the `dtype` of :paramref:`a`.
See Also
--------
max
"""
if a.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in nanmax")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.NAN_MAX, axis, oneshot=oneshot, cfg=cfg, **kwargs)
def nanmin(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
oneshot: bool = False,
cfg: ia.Config = None,
**kwargs,
):
"""
Return the minimum of an array or minimum along an axis ignoring NaNs.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a floating-point data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
oneshot : bool
Enforce the use of the oneshot algorithm. Oneshot normally uses less memory,
albeit is slower in general. Default is False.
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
min : :ref:`IArray`
Minimum of a. The result is
an array of dimension a.ndim - len(axis). The `dtype` is always the `dtype` of :paramref:`a`.
See Also
--------
min
"""
if a.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in nanmin")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.NAN_MIN, axis, oneshot=oneshot, cfg=cfg, **kwargs)
def nansum(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
oneshot: bool = False,
cfg: ia.Config = None,
**kwargs,
):
"""
Return the sum of array elements over a given axis ignoring NaNs.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a floating-point data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
oneshot : bool
Enforce the use of the oneshot algorithm. Oneshot normally uses less memory,
albeit is slower in general. Default is False.
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
sum : :ref:`IArray`
Sum of a. The result is
an array of dimension a.ndim - len(axis). The `dtype` is always the `dtype` of :paramref:`a`.
See Also
--------
sum
"""
if a.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in nansum")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.NAN_SUM, axis, oneshot=oneshot, cfg=cfg, **kwargs)
def nanprod(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
oneshot: bool = False,
cfg: ia.Config = None,
**kwargs,
):
"""Return the product of array elements over a given axis ignoring NaNs.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a floating-point data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
oneshot : bool
Enforce the use of the oneshot algorithm. Oneshot normally uses less memory,
albeit is slower in general. Default is False.
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
prod : :ref:`IArray`
Product of a. The result is
an array of dimension a.ndim - len(axis). The `dtype` is always the `dtype` of :paramref:`a`.
See Also
--------
prod
"""
if a.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in nanprod")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.NAN_PROD, axis, oneshot=oneshot, cfg=cfg, **kwargs)
def nanmean(a: IArray, /, *, axis: Union[int, tuple] = None, cfg: ia.Config = None, **kwargs):
"""Compute the arithmetic mean along the specified axis ignoring NaNs.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a floating-point data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
mean : :ref:`IArray`
Mean of a. The result is
an array of dimension a.ndim - len(axis). The `dtype` is always the `dtype` of :paramref:`a`.
See Also
--------
mean
"""
if a.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in nanmean")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.NAN_MEAN, axis, oneshot=True, cfg=cfg, **kwargs)
def nanstd(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
correction: Union[int, float] = 0.0,
cfg: ia.Config = None,
**kwargs,
):
"""Returns the standard deviation ignoring NaNs.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a floating-point data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
correction : int or float
Degrees of freedom adjustment. Default is 0.
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
std : :ref:`IArray`
Standard deviation of a. The result is
an array of dimension a.ndim - len(axis). The `dtype` is always the `dtype` of :paramref:`a`.
See Also
--------
std
"""
if a.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in nanstd")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(
a, ia.Reduce.NAN_STD, axis, oneshot=True, correction=correction, cfg=cfg, **kwargs
)
def nanvar(
a: IArray,
/,
*,
axis: Union[int, tuple] = None,
correction: Union[int, float] = 0.0,
cfg: ia.Config = None,
**kwargs,
):
"""Compute the variance along the specified axis ignoring NaNs. The variance is computed for the flattened
array by default, otherwise over the specified axis.
Parameters
----------
a : :ref:`IArray`
Input data. Should have a floating-point data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
correction : int or float
Degrees of freedom adjustment. Default is 0.
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
var : :ref:`IArray`
Variance of a. The result is
an array of dimension a.ndim - len(axis). The `dtype` is always the `dtype` of :paramref:`a`.
See Also
--------
var
"""
if a.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in nanvar")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(
a, ia.Reduce.NAN_VAR, axis, oneshot=True, correction=correction, cfg=cfg, **kwargs
)
def nanmedian(a: IArray, /, *, axis: Union[int, tuple] = None, cfg: ia.Config = None, **kwargs):
"""Compute the median ignoring NaNs along the specified axis. Returns the median of the array elements.
Parameters
----------
a(self) : :ref:`IArray`
Input data. Should have a floating-point data type.
axis : None, int, tuple of ints, optional
Axis or axes along which the reduction is performed. The default (axis = None) is perform
the reduction over all dimensions of the input array.
If this is a tuple of ints, a reduction is performed on multiple axes, instead of a single
axis or all the axes as default.
cfg : :class:`Config` or None
The configuration for this operation. If None (default), the current configuration will be
used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config` dataclass that should
override the current configuration.
Returns
-------
median : :ref:`IArray`
Median of a. The result is
an array of dimension a.ndim - len(axis). The `dtype` is always the `dtype` of :paramref:`a`.
See Also
--------
median
"""
if a.dtype not in _floating_dtypes:
raise TypeError("Only floating dtypes are allowed in nanmedian")
if cfg is None:
cfg = a.cfg
cfg.urlpath = None
with ia.config(cfg=cfg) as cfg:
return reduce(a, ia.Reduce.NAN_MEDIAN, axis, oneshot=True, cfg=cfg, **kwargs)
# Linear Algebra
def opt_gemv(a: IArray, b: IArray, cfg=None, **kwargs):
shape = (a.shape[0], b.shape[1]) if b.ndim == 2 else (a.shape[0],)
if cfg is None:
cfg = ia.get_config_defaults()
with ia.config(shape=shape, cfg=cfg, **kwargs) as cfg:
return ext.opt_gemv(cfg, a, b)
def matmul_params(ashape, bshape, dtype=None, l2_size=None, chunk_size=128 * 1024 * 1024):
"""
Given a matrix multiplication of two arrays, it computes the chunks and the blocks of the operands
to use an optimized version of the matmul algorithm.
Parameters
----------
ashape: tuple or list
The shape of the operand a.
bshape: tuple or list
The shape of the operand b.
dtype:
The dtype of each item.
l2_size: int
The size of the l2 cache. It is used to compute the size of the blocks.
chunk_size: int
The maximum chunksize allowed. It is used to compute the size of the chunks.
Returns
-------
params: tuple
A tuple specifying the chunks and the blocks of the matmul operands a and b
(achunks, ablocks, bchunks, bblocks).
"""
if not dtype:
dtype = ia.get_config_defaults().dtype
itemsize = np.dtype(dtype).itemsize
if not l2_size:
l2_size = ia.get_l2_size()
l2_size = l2_size // 2
# The above operation is based on the following results:
# Matmul performance on Intel(R) Core(TM) i9-10940X CPU @ 3.30GHz (14 cores, 28 logical)
# Time (L2 size = 65536) 4.27 s
# Time (L2 size = 131072) 2.53 s
# Time (L2 size = 262144) 1.81 s
# Time (L2 size = 524288) 1.89 s
# Time (L2 size = 1048576) 4.68 s <- CPU L2 size
# Time (L2 size = 2097152) 4.06 s
if len(ashape) != 2:
raise AttributeError("The dimension of a must be 2")
if len(bshape) != 1 and len(bshape) != 2:
raise AttributeError("The dimension of b must be 1 or 2")
if ashape[1] != bshape[0]:
raise AttributeError("ashape[1] must be equal to bshape[0]")
if len(bshape) == 1:
return matmul_gemv_params(ashape[0], ashape[1], itemsize, l2_size, chunk_size)
else:
return matmul_gemm_params(ashape[0], ashape[1], bshape[1], itemsize, l2_size, chunk_size)
def matmul_gemv_params(M, N, itemsize=8, l2_size=512 * 1024, chunk_size=128 * 1024 * 1024):
"""
Given a matmul operation a * b = c, it computes the chunks and the blocks of the operands
(a and b) to use an optimized version of the matmul algorithm.
Parameters
----------
M: int
Specifies the number of rows of the matrix a and of the matrix c. M must be at least zero.
N: int
Specifies the number of columns of the matrix a and the number of rows of the vector b.
itemsize:
The size of each item.
l2_size: int
The size of the l2 cache. It is used to compute the size of the blocks.
chunk_size: int
The maximum chunksize allowed. It is used to compute the size of the chunks.
Returns
-------
params: tuple
A tuple specifying the chunks and the blocks of the matmul operands a and b
(achunks, ablocks, bchunks, bblocks).
"""
l2_nelem = l2_size // itemsize
block_nelem_dim = int(-1 + np.sqrt(1 + l2_nelem))
n_block = block_nelem_dim
if n_block > N:
n_block = N
m_block = block_nelem_dim
if m_block > M:
m_block = M
chunk_nelem = chunk_size // itemsize
chunk_nelem_dim = int(np.sqrt(chunk_nelem))
n_chunk = chunk_nelem_dim
if n_chunk % n_block != 0:
n_chunk = (n_chunk // n_block + 1) * n_block
if n_chunk > N:
if N % n_block == 0:
n_chunk = N
else:
n_chunk = (N // n_block + 1) * n_block
m_chunk = chunk_nelem_dim
if m_chunk % m_block != 0:
m_chunk = (m_chunk // m_block + 1) * m_block
if m_chunk > M:
if M % m_block == 0:
m_chunk = M
else:
m_chunk = (M // m_block + 1) * m_block
a_chunks = (m_chunk, n_chunk)
a_blocks = (m_block, n_block)
b_chunks = (n_chunk,)
b_blocks = (n_block,)
return a_chunks, a_blocks, b_chunks, b_blocks
def matmul_gemm_params(M, K, N, itemsize=8, l2_size=512 * 1024, chunk_size=128 * 1024 * 1024):
"""
Given a matmul operation a * b = c, it computes the chunks and the blocks of the operands
(a and b) to use an optimized version of the matmul algorithm.
Parameters
----------
M: int
Specifies the number of rows of the matrix A and of the matrix C. M must be at least zero.
K: int
Specifies the number of columns of the matrix A and the number of rows of the matrix B.
K must be at least zero.
N: int
Specifies the number of columns of the matrix B and the number of columns of the matrix C.
N must be at least zero.
itemsize: int
The size of each item.
l2_size: int
The size of the l2 cache. It is used to compute the size of the blocks.
chunk_size: int
The maximum chunksize allowed. It is used to compute the size of the chunks.
Returns
-------
params: tuple
A tuple specifying the chunks and the blocks of the matmul operands a and b
(achunks, ablocks, bchunks, bblocks).
"""
l2_nelem = l2_size // itemsize
block_nelem = l2_nelem // 3
block_nelem_dim = int(np.sqrt(block_nelem))
n_block = block_nelem_dim
if n_block > N:
n_block = N
m_block = block_nelem_dim
if m_block > M:
m_block = M
k_block = block_nelem_dim
if k_block > K:
k_block = K
chunk_nelem = chunk_size // itemsize
chunk_nelem_dim = int(np.sqrt(chunk_nelem))
n_chunk = chunk_nelem_dim
if n_chunk % n_block != 0:
n_chunk = (n_chunk // n_block + 1) * n_block
if n_chunk > N:
if N % n_block == 0:
n_chunk = N
else:
n_chunk = (N // n_block + 1) * n_block
m_chunk = chunk_nelem_dim
if m_chunk % m_block != 0:
m_chunk = (m_chunk // m_block + 1) * m_block
if m_chunk > M:
if M % m_block == 0:
m_chunk = M
else:
m_chunk = (M // m_block + 1) * m_block
k_chunk = chunk_nelem_dim
if k_chunk % k_block != 0:
k_chunk = (k_chunk // k_block + 1) * k_block
if k_chunk > K:
if K % k_block == 0:
k_chunk = K
else:
k_chunk = (K // k_block + 1) * k_block
a_chunks = (m_chunk, k_chunk)
b_chunks = (k_chunk, n_chunk)
a_blocks = (m_block, k_block)
b_blocks = (k_block, n_block)
return a_chunks, a_blocks, b_chunks, b_blocks
def matmul(a: IArray, b: IArray, /, *, cfg=None, **kwargs):
"""Multiply two matrices.
Parameters
----------
a : :ref:`IArray`
First array. Should have a numeric data type.
b : :ref:`IArray`
Second array. Should have a numeric data type.
cfg : :class:`Config`
The configuration for running the expression.
If None (default), global defaults are used.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config`
dataclass that should override the current configuration.
Returns
-------
:ref:`IArray`
The resulting array.
"""
a._check_allowed_dtypes(b, "numeric", "matmul")
if 0 in [a.ndim, b.ndim]:
raise AttributeError("arrays must have at least one dimension")
if a.ndim == 1:
raise NotImplementedError("`a` must be two dimensional")
shape = (a.shape[0], b.shape[1]) if b.ndim == 2 else (a.shape[0],)
if cfg is None:
cfg = ia.get_config_defaults()
if (
a.chunks
and b.chunks
and a.chunks[0] % a.blocks[0] == 0
and a.chunks[1] % a.blocks[1] == 0
and b.chunks[0] % b.blocks[0] == 0
and a.chunks[1] == b.chunks[0]
and a.blocks[1] == a.blocks[0]
and "chunks" not in kwargs
and "blocks" not in kwargs
):
if b.ndim == 1:
kwargs["chunks"] = (a.chunks[0],)
kwargs["blocks"] = (a.blocks[0],)
with ia.config(shape=shape, cfg=cfg, **kwargs) as cfg:
return ext.opt_gemv(cfg, a, b)
elif b.ndim == 2 and b.chunks[1] % b.blocks[1] == 0:
kwargs["chunks"] = (a.chunks[0], b.chunks[1])
kwargs["blocks"] = (a.blocks[0], b.blocks[1])
with ia.config(shape=shape, cfg=cfg, **kwargs) as cfg:
return ext.opt_gemm(cfg, a, b)
with ia.config(shape=shape, cfg=cfg, **kwargs) as cfg:
return ext.matmul(cfg, a, b)
def matrix_transpose(a: IArray, /, *, cfg=None, **kwargs):
"""Transpose an array.
Parameters
----------
a : :ref:`IArray`
The array to transpose.
cfg : :class:`Config`
The configuration for running the expression.
If None (default), global defaults are used. The `np_dtype` will be the one
from :paramref:`a`.
kwargs : dict
A dictionary for setting some or all of the fields in the :class:`Config`
dataclass that should override the current configuration.
Returns
-------
:ref:`IArray`
The transposed array.
"""
if a.ndim != 2:
raise AttributeError("Array dimension must be 2")
if cfg is None:
cfg = ia.get_config_defaults()
with ia.config(cfg=cfg, **kwargs) as cfg:
return ext.transpose(cfg, a)