Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6cb7859
feat: add dtype objects, backend properties, and dtype registration
nicola-bastianello Jun 12, 2026
9823697
docs: added discussion of supported dtypes
nicola-bastianello Jun 12, 2026
2767bad
Merge remote-tracking branch 'origin/main' into dtypes
nicola-bastianello Jun 12, 2026
1c18f8a
enh: next iteration of dtypes
nicola-bastianello Jun 14, 2026
b987a82
ref: change enums names
nicola-bastianello Jun 15, 2026
b963ff8
enh: change default dtype names
nicola-bastianello Jun 15, 2026
41aed3b
enh: first working version
nicola-bastianello Jun 17, 2026
86212da
enh: improvements
nicola-bastianello Jun 17, 2026
249993f
enh: added filtering in dtypes function
nicola-bastianello Jun 17, 2026
cf6c260
enh: user guide
nicola-bastianello Jun 17, 2026
8478f20
enh: added constants
nicola-bastianello Jun 18, 2026
9c189d1
fix: tests and sphinx
nicola-bastianello Jun 18, 2026
3d02be1
enh: add test for dtypes
nicola-bastianello Jun 18, 2026
f40712f
enh: add backend name and utils submodule
nicola-bastianello Jun 18, 2026
4066770
enh: dtypes docstring
nicola-bastianello Jun 18, 2026
5f68671
fix: remove ruff preview rules
nicola-bastianello Jun 19, 2026
b67c251
fix: address PR comments
nicola-bastianello Jul 1, 2026
3d1f1f1
fix: added version control
nicola-bastianello Jul 1, 2026
e259113
fix: properly lock versions, improve pyproject.toml, fix numpy-relate…
nicola-bastianello Jul 6, 2026
2417bc7
enh: add item to array, base coercion dunders on item
nicola-bastianello Jul 6, 2026
8542728
enh: unify error messaging
nicola-bastianello Jul 6, 2026
947f6ee
Merge branch 'main' into coercion-2
nicola-bastianello Aug 12, 2026
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
20 changes: 15 additions & 5 deletions decent_array/_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,23 +355,23 @@ def __len__(self) -> int:

def __float__(self) -> float:
"""Coerce a scalar array to a Python float."""
return float(self._backend.squeeze(self).value)
return float(self.item())

def __bool__(self) -> bool:
"""Coerce a scalar array to a Python bool."""
return bool(self._backend.squeeze(self).value)
return bool(self.item())

def __int__(self) -> int:
"""Coerce a scalar array to a Python int."""
return int(self._backend.squeeze(self).value)
return int(self.item())

def __complex__(self) -> complex:
"""Coerce a scalar array to a Python complex."""
return complex(self._backend.squeeze(self).value)
return complex(self.item())

def __index__(self) -> int:
"""Coerce a scalar array to a Python int."""
return int(self._backend.squeeze(self).value)
return int(self.item())

# Repr -----------------------------------------------------------------

Expand Down Expand Up @@ -444,3 +444,13 @@ def device(self) -> Devices:
def numpy(self) -> NDArray[Any]:
"""Return a NumPy array view of the array's data."""
return self._backend.to_numpy(self)

def item(self) -> Any: # noqa: ANN401
"""
Convert 0-dim array to Python scalar.

Raises:
TypeError: if ``x`` is not 0-dimensional.

"""
return self._backend.to_scalar(self)
37 changes: 37 additions & 0 deletions decent_array/_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Common errors used across backends."""

from typing import Any

from mypy_extensions import mypyc_attr


@mypyc_attr(native_class=False)
class NotScalarError(TypeError):
def __init__(self, ndim: int):
super().__init__(f"Only 0-dim arrays can be converted to Python scalars, got {ndim}-dim array.")


class NDimError(ValueError):
def __init__(self, required_ndim: int, actual_ndim: int):
super().__init__(f"A {required_ndim}-dim array is required, got {actual_ndim}-dim array.")


class MatrixTransposeError(ValueError):
def __init__(self, ndim: int):
super().__init__(f"An aray with at least 2 dimensions is required, got {ndim}-dim array.")


class UnsupportedDTypeCreationError(ValueError):
def __init__(self, dtype: Any, backend_name: str, device_name: str): # noqa: ANN401
super().__init__(f"Unsupported dtype '{dtype}' for {backend_name} on {device_name}.")


class UnsupportedDeviceError(ValueError):
def __init__(self, backend_name: str, device_name: str):
super().__init__(f"{backend_name} does not support device '{device_name}'.")


stack_empty_error = ValueError("Cannot stack an empty sequence of arrays.")


no_backend_error = RuntimeError("No backend active: call 'set_backend' with a supported framework to activate one.")
5 changes: 5 additions & 0 deletions decent_array/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ def unwrap(x: Any) -> Any: # noqa: ANN401
site without runtime benefit.
"""
return x.value if type(x) is Array else x


def is_scalar(x: Array) -> bool:
"""Return True if ``x`` is a 0-dim Array."""
return x.ndim == 0
8 changes: 8 additions & 0 deletions decent_array/interoperability/_abstracts/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ def from_numpy_like(self, x: NDArray[Any], like: Array) -> Array:
def asarray(self, x: bool | int | float | complex) -> Array:
"""Convert a Python scalar to an :class:`Array` on this backend."""

@abstractmethod
def to_scalar(self, x: Array) -> Any: # noqa: ANN401
"""
Convert a 0-dim array to a scalar.

This method must use ``is_scalar(x)`` to raise when ``x`` is not 0-dim.
"""

@abstractmethod
def stack(self, arrays: Sequence[Array], axis: int = 0) -> Array:
"""Stack a sequence of arrays along a new dimension."""
Expand Down
13 changes: 7 additions & 6 deletions decent_array/interoperability/_iop/bit_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from typing import TYPE_CHECKING

from decent_array._errors import no_backend_error
from decent_array.interoperability._backend_manager import register_backend_listener

if TYPE_CHECKING:
Expand All @@ -36,40 +37,40 @@ def _update_backend(backend: Backend | None) -> None:
def bitwise_and(x1: bool | int | Array, x2: bool | int | Array) -> Array:
"""Element-wise bitwise/logical AND."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.bitwise_and(x1, x2)


def bitwise_invert(x: Array) -> Array:
"""Element-wise bitwise/logical NOT."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.bitwise_invert(x)


def bitwise_or(x1: bool | int | Array, x2: bool | int | Array) -> Array:
"""Element-wise bitwise/logical OR."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.bitwise_or(x1, x2)


def bitwise_xor(x1: bool | int | Array, x2: bool | int | Array) -> Array:
"""Element-wise bitwise/logical XOR."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.bitwise_xor(x1, x2)


def bitwise_left_shift(x1: int | Array, x2: int | Array) -> Array:
"""Element-wise bitwise left shift."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.bitwise_left_shift(x1, x2)


def bitwise_right_shift(x1: int | Array, x2: int | Array) -> Array:
"""Element-wise bitwise right shift."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.bitwise_right_shift(x1, x2)
14 changes: 7 additions & 7 deletions decent_array/interoperability/_iop/comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@

from typing import TYPE_CHECKING

from decent_array._errors import no_backend_error
from decent_array.interoperability._backend_manager import register_backend_listener

if TYPE_CHECKING:
from decent_array import Array
from decent_array.interoperability._abstracts import Backend

_BACKEND_INSTANCE: Backend | None = None
_error = RuntimeError("No backend active: call 'set_backend' with a supported framework to activate one.")


def _update_backend(backend: Backend | None) -> None:
Expand All @@ -36,40 +36,40 @@ def _update_backend(backend: Backend | None) -> None:
def equal(x1: int | float | complex | Array, x2: int | float | complex | Array) -> Array:
"""Element-wise equality. Returns an :class:`~decent_array.Array` of bools."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.equal(x1, x2)


def not_equal(x1: int | float | complex | Array, x2: int | float | complex | Array) -> Array:
"""Element-wise inequality. Returns an :class:`~decent_array.Array` of bools."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.not_equal(x1, x2)


def less(x1: int | float | complex | Array, x2: int | float | complex | Array) -> Array:
"""Element-wise less-than. Returns an :class:`~decent_array.Array` of bools."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.less(x1, x2)


def less_equal(x1: int | float | complex | Array, x2: int | float | complex | Array) -> Array:
"""Element-wise less-than-or-equal. Returns an :class:`~decent_array.Array` of bools."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.less_equal(x1, x2)


def greater(x1: int | float | complex | Array, x2: int | float | complex | Array) -> Array:
"""Element-wise greater-than. Returns an :class:`~decent_array.Array` of bools."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.greater(x1, x2)


def greater_equal(x1: int | float | complex | Array, x2: int | float | complex | Array) -> Array:
"""Element-wise greater-than-or-equal. Returns an :class:`~decent_array.Array` of bools."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.greater_equal(x1, x2)
12 changes: 6 additions & 6 deletions decent_array/interoperability/_iop/creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@

from typing import TYPE_CHECKING

from decent_array._errors import no_backend_error
from decent_array.interoperability._backend_manager import register_backend_listener

if TYPE_CHECKING:
from decent_array import Array
from decent_array.interoperability._abstracts import Backend

_BACKEND_INSTANCE: Backend | None = None
_error = RuntimeError("No backend active: call 'set_backend' with a supported framework to activate one.")


def _update_backend(backend: Backend | None) -> None:
Expand All @@ -36,33 +36,33 @@ def _update_backend(backend: Backend | None) -> None:
def zeros(shape: int | tuple[int, ...]) -> Array:
"""Create an array of zeros with the given shape."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.zeros(shape)


def zeros_like(x: Array) -> Array:
"""Create an array of zeros matching the shape and type of ``x``."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.zeros_like(x)


def ones(shape: int | tuple[int, ...]) -> Array:
"""Create an array of ones with the given shape."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.ones(shape)


def ones_like(x: Array) -> Array:
"""Create an array of ones matching the shape and type of ``x``."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.ones_like(x)


def eye(n: int) -> Array:
"""Create an ``n x n`` identity matrix."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.eye(n)
12 changes: 6 additions & 6 deletions decent_array/interoperability/_iop/linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@

from typing import TYPE_CHECKING

from decent_array._errors import no_backend_error
from decent_array.interoperability._backend_manager import register_backend_listener

if TYPE_CHECKING:
from decent_array import Array
from decent_array.interoperability._abstracts import Backend

_BACKEND_INSTANCE: Backend | None = None
_error = RuntimeError("No backend active: call 'set_backend' with a supported framework to activate one.")


def _update_backend(backend: Backend | None) -> None:
Expand All @@ -36,7 +36,7 @@ def _update_backend(backend: Backend | None) -> None:
def vecdot(x1: Array, x2: Array) -> Array:
"""Vector dot product of two arrays."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.vecdot(x1, x2)


Expand All @@ -47,14 +47,14 @@ def dot(x1: Array, x2: Array) -> Array:
Alias for :func:`vecdot`.
"""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.vecdot(x1, x2)


def matmul(x1: Array, x2: Array) -> Array:
"""Matrix multiplication of two arrays."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.matmul(x1, x2)


Expand All @@ -66,7 +66,7 @@ def vector_norm(
) -> Array:
"""Vector norm of ``x``."""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.vector_norm(x, axis, keepdims, ord)


Expand All @@ -82,5 +82,5 @@ def norm(
Alias for :func:`vector_norm`.
"""
if _BACKEND_INSTANCE is None:
raise _error
raise no_backend_error
return _BACKEND_INSTANCE.vector_norm(x, axis, keepdims, ord)
Loading
Loading