ndlike 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ndlike/__init__.py +1 -0
- ndlike/_switchdispatch.py +208 -0
- ndlike/array_like.py +471 -0
- ndlike/jax_functions.py +999 -0
- ndlike/jax_like.py +547 -0
- ndlike/jax_operators.py +104 -0
- ndlike/protected_axis/__init__.py +0 -0
- ndlike/protected_axis/_common_functions.py +374 -0
- ndlike/protected_axis/jax.py +517 -0
- ndlike/protected_axis/jax_functions.py +737 -0
- ndlike/protected_axis/numpy.py +514 -0
- ndlike/protected_axis/numpy_functions.py +474 -0
- ndlike/protected_axis/torch.py +387 -0
- ndlike/protected_axis/torch_functions.py +681 -0
- ndlike/py.typed +0 -0
- ndlike/torch_functions.py +154 -0
- ndlike/torch_like.py +346 -0
- ndlike-0.1.0.dist-info/METADATA +160 -0
- ndlike-0.1.0.dist-info/RECORD +22 -0
- ndlike-0.1.0.dist-info/WHEEL +5 -0
- ndlike-0.1.0.dist-info/licenses/LICENSE +21 -0
- ndlike-0.1.0.dist-info/top_level.txt +1 -0
ndlike/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tools for defining custom array types across NumPy, PyTorch, and JAX."""
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""An extensible switch dispatch mechanism.
|
|
2
|
+
|
|
3
|
+
Akin to the builtin functools.singledispatch mechanism,
|
|
4
|
+
but with an equality-based dispatcher.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Iterator, MutableMapping
|
|
10
|
+
from functools import update_wrapper
|
|
11
|
+
from typing import TYPE_CHECKING, Concatenate, Protocol, overload, override
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from collections.abc import Callable, Iterable
|
|
15
|
+
from types import NotImplementedType
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _RegistrationSink[K, V](Protocol):
|
|
19
|
+
"""The contravariant write interface used to constrain registered values."""
|
|
20
|
+
|
|
21
|
+
def _register_value(self, key: K, value: V) -> None: ...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _Registration[V]:
|
|
25
|
+
"""Store a value while preserving its exact type and identity."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, store: Callable[[V], None]) -> None:
|
|
28
|
+
self._store = store
|
|
29
|
+
|
|
30
|
+
# A bound such as ``W: V`` is not legal: TypeVar bounds cannot be generic.
|
|
31
|
+
# Contravariant storage constrains W to accepted values without widening it
|
|
32
|
+
# to V, preserving generic classes, overloads, and callable-object attributes.
|
|
33
|
+
def __call__[W](self: _Registration[W], value: W) -> W:
|
|
34
|
+
self._store(value)
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class _FunctionRegistration[F]:
|
|
39
|
+
"""Identity-preserving registration with the function decorator's ``f`` keyword."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, store: Callable[[F], None]) -> None:
|
|
42
|
+
self._store = store
|
|
43
|
+
|
|
44
|
+
def __call__[W](self: _FunctionRegistration[W], f: W) -> W:
|
|
45
|
+
self._store(f)
|
|
46
|
+
return f
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class switchdispatch[T, **In, Out]: # noqa: N801
|
|
50
|
+
"""A switch dispatch decorator.
|
|
51
|
+
|
|
52
|
+
Similar to functools.singledispatch, but dispatches based on equality
|
|
53
|
+
rather than type.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
>>> @switchdispatch
|
|
57
|
+
>>> def func(x):
|
|
58
|
+
>>> return "default"
|
|
59
|
+
>>>
|
|
60
|
+
>>> @func.register(1)
|
|
61
|
+
>>> def _(x):
|
|
62
|
+
>>> return "one"
|
|
63
|
+
>>>
|
|
64
|
+
>>> @func.register(2)
|
|
65
|
+
>>> def _(x):
|
|
66
|
+
>>> return "two"
|
|
67
|
+
>>>
|
|
68
|
+
>>> print(func(1)) # Output: "one"
|
|
69
|
+
>>> print(func(2)) # Output: "two"
|
|
70
|
+
>>> print(func(3)) # Output: "default"
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, func: Callable[Concatenate[T, In], Out]) -> None:
|
|
74
|
+
"""Initialize the switchdispatch with the default function."""
|
|
75
|
+
self._func = func
|
|
76
|
+
self._registry: dict[object, Callable[Concatenate[T, In], Out]] = {}
|
|
77
|
+
update_wrapper(self, func, updated=())
|
|
78
|
+
|
|
79
|
+
@overload
|
|
80
|
+
def register(self, key: T) -> _FunctionRegistration[Callable[Concatenate[T, In], Out]]: ...
|
|
81
|
+
|
|
82
|
+
@overload
|
|
83
|
+
def register[F](self: _RegistrationSink[T, F], key: T, f: F) -> F: ...
|
|
84
|
+
|
|
85
|
+
def register(
|
|
86
|
+
self,
|
|
87
|
+
key: object,
|
|
88
|
+
f: Callable[Concatenate[T, In], Out] | None = None,
|
|
89
|
+
) -> Callable[Concatenate[T, In], Out] | _FunctionRegistration[Callable[Concatenate[T, In], Out]]:
|
|
90
|
+
"""Register a function for the given key, returning it unchanged."""
|
|
91
|
+
if f is None:
|
|
92
|
+
return self.multi_register([key])
|
|
93
|
+
return self.multi_register([key], f)
|
|
94
|
+
|
|
95
|
+
@overload
|
|
96
|
+
def multi_register(self, keys: Iterable[object]) -> _FunctionRegistration[Callable[Concatenate[T, In], Out]]: ...
|
|
97
|
+
|
|
98
|
+
@overload
|
|
99
|
+
def multi_register[F](self: _RegistrationSink[object, F], keys: Iterable[object], f: F) -> F: ...
|
|
100
|
+
|
|
101
|
+
def multi_register(
|
|
102
|
+
self,
|
|
103
|
+
keys: Iterable[object],
|
|
104
|
+
f: Callable[Concatenate[T, In], Out] | None = None,
|
|
105
|
+
) -> Callable[Concatenate[T, In], Out] | _FunctionRegistration[Callable[Concatenate[T, In], Out]]:
|
|
106
|
+
"""Register a function for the given keys, returning it unchanged."""
|
|
107
|
+
if f is not None:
|
|
108
|
+
for key in keys:
|
|
109
|
+
self._register_value(key, f)
|
|
110
|
+
return f
|
|
111
|
+
|
|
112
|
+
def store(f: Callable[Concatenate[T, In], Out]) -> None:
|
|
113
|
+
for key in keys:
|
|
114
|
+
self._register_value(key, f)
|
|
115
|
+
|
|
116
|
+
return _FunctionRegistration(store)
|
|
117
|
+
|
|
118
|
+
def _register_value(self, key: object, value: Callable[Concatenate[T, In], Out]) -> None:
|
|
119
|
+
self._registry[key] = value
|
|
120
|
+
|
|
121
|
+
def __call__(self, arg: T, *args: In.args, **kwargs: In.kwargs) -> Out:
|
|
122
|
+
"""Call the appropriate function based on the argument."""
|
|
123
|
+
func_impl = self._registry.get(arg, self._func)
|
|
124
|
+
return func_impl(arg, *args, **kwargs)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class switch[K, V](MutableMapping[K, V]): # noqa: N801
|
|
128
|
+
"""A simple switch-case mechanism based on a dictionary."""
|
|
129
|
+
|
|
130
|
+
_registry: dict[K, V]
|
|
131
|
+
|
|
132
|
+
def __init__(
|
|
133
|
+
self,
|
|
134
|
+
cases: dict[K, V] | None = None,
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Initialize the switch with the given cases and default value."""
|
|
137
|
+
self._registry = cases or {}
|
|
138
|
+
|
|
139
|
+
@overload
|
|
140
|
+
def register(self, key: K) -> _Registration[V]: ...
|
|
141
|
+
|
|
142
|
+
@overload
|
|
143
|
+
def register[W](self: _RegistrationSink[K, W], key: K, value: W) -> W: ...
|
|
144
|
+
|
|
145
|
+
def register(
|
|
146
|
+
self,
|
|
147
|
+
key: K,
|
|
148
|
+
value: V | NotImplementedType = NotImplemented,
|
|
149
|
+
) -> _Registration[V] | V:
|
|
150
|
+
"""Register a value for the given key, returning it unchanged."""
|
|
151
|
+
if value is NotImplemented:
|
|
152
|
+
return self.multi_register([key])
|
|
153
|
+
return self.multi_register([key], value)
|
|
154
|
+
|
|
155
|
+
@overload
|
|
156
|
+
def multi_register(self, keys: Iterable[K]) -> _Registration[V]: ...
|
|
157
|
+
|
|
158
|
+
@overload
|
|
159
|
+
def multi_register[W](self: _RegistrationSink[K, W], keys: Iterable[K], value: W) -> W: ...
|
|
160
|
+
|
|
161
|
+
def multi_register(
|
|
162
|
+
self,
|
|
163
|
+
keys: Iterable[K],
|
|
164
|
+
value: V | NotImplementedType = NotImplemented,
|
|
165
|
+
) -> _Registration[V] | V:
|
|
166
|
+
"""Register a value for the given keys, returning it unchanged."""
|
|
167
|
+
if value is not NotImplemented:
|
|
168
|
+
for key in keys:
|
|
169
|
+
self._register_value(key, value)
|
|
170
|
+
return value
|
|
171
|
+
|
|
172
|
+
def store(value: V) -> None:
|
|
173
|
+
for key in keys:
|
|
174
|
+
self._register_value(key, value)
|
|
175
|
+
|
|
176
|
+
return _Registration(store)
|
|
177
|
+
|
|
178
|
+
def _register_value(self, key: K, value: V) -> None:
|
|
179
|
+
self._registry[key] = value
|
|
180
|
+
|
|
181
|
+
@override
|
|
182
|
+
def __getitem__(self, key: K) -> V:
|
|
183
|
+
"""Return the value corresponding to the given key."""
|
|
184
|
+
return self._registry[key]
|
|
185
|
+
|
|
186
|
+
def __call__(self, key: K) -> V:
|
|
187
|
+
"""Return the value corresponding to the given key."""
|
|
188
|
+
return self._registry[key]
|
|
189
|
+
|
|
190
|
+
@override
|
|
191
|
+
def __setitem__(self, key: K, value: V) -> None:
|
|
192
|
+
"""Set the value for the given key."""
|
|
193
|
+
self._registry[key] = value
|
|
194
|
+
|
|
195
|
+
@override
|
|
196
|
+
def __delitem__(self, key: K) -> None:
|
|
197
|
+
"""Delete the value for the given key."""
|
|
198
|
+
del self._registry[key]
|
|
199
|
+
|
|
200
|
+
@override
|
|
201
|
+
def __iter__(self) -> Iterator[K]:
|
|
202
|
+
"""Return an iterator over the keys in the switch cases."""
|
|
203
|
+
return iter(self._registry)
|
|
204
|
+
|
|
205
|
+
@override
|
|
206
|
+
def __len__(self) -> int:
|
|
207
|
+
"""Return the number of cases in the switch."""
|
|
208
|
+
return len(self._registry)
|
ndlike/array_like.py
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
"""Protocol for ndarray-like objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from collections.abc import Callable, Iterator, Sequence
|
|
7
|
+
from types import EllipsisType, ModuleType
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Literal, Protocol, Self, SupportsIndex, override, runtime_checkable
|
|
9
|
+
|
|
10
|
+
from flextype import flexdispatch
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from numpy.typing import ArrayLike as NpArrayLike, DTypeLike, NDArray
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class _SupportsArray[T: np.dtype](Protocol):
|
|
19
|
+
def __array__(self) -> np.ndarray[Any, T]: ...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
type _NestedSequence[T] = T | Sequence[_NestedSequence[T]]
|
|
23
|
+
|
|
24
|
+
type _DualArrayLike[DT: np.dtype, T] = _NestedSequence[_SupportsArray[DT]] | _NestedSequence[T]
|
|
25
|
+
type _ArrayLikeInt_co = _DualArrayLike[np.dtype[np.bool | np.integer], int]
|
|
26
|
+
type ToIndex = SupportsIndex | slice | EllipsisType | _ArrayLikeInt_co | None
|
|
27
|
+
type ToIndices = ToIndex | tuple[ToIndex, ...]
|
|
28
|
+
type Order = Literal["C", "F", "A", "K"]
|
|
29
|
+
type ArrayAPIVersion = Literal["2022.12", "2023.12", "2024.12", "2025.12"]
|
|
30
|
+
|
|
31
|
+
_GetFlag = Literal[
|
|
32
|
+
"C",
|
|
33
|
+
"CONTIGUOUS",
|
|
34
|
+
"C_CONTIGUOUS",
|
|
35
|
+
"F",
|
|
36
|
+
"FORTRAN",
|
|
37
|
+
"F_CONTIGUOUS",
|
|
38
|
+
"W",
|
|
39
|
+
"WRITEABLE",
|
|
40
|
+
"B",
|
|
41
|
+
"BEHAVED",
|
|
42
|
+
"O",
|
|
43
|
+
"OWNDATA",
|
|
44
|
+
"A",
|
|
45
|
+
"ALIGNED",
|
|
46
|
+
"X",
|
|
47
|
+
"WRITEBACKIFCOPY",
|
|
48
|
+
"CA",
|
|
49
|
+
"CARRAY",
|
|
50
|
+
"FA",
|
|
51
|
+
"FARRAY",
|
|
52
|
+
"FNC",
|
|
53
|
+
"FORC",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
_SetFlag = Literal[
|
|
57
|
+
"A",
|
|
58
|
+
"ALIGNED",
|
|
59
|
+
"W",
|
|
60
|
+
"WRITEABLE",
|
|
61
|
+
"X",
|
|
62
|
+
"WRITEBACKIFCOPY",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ArrayFlagsLike(Protocol):
|
|
67
|
+
"""Protocol for array flags."""
|
|
68
|
+
|
|
69
|
+
aligned: bool
|
|
70
|
+
writeable: bool
|
|
71
|
+
writebackifcopy: bool
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def behaved(self) -> bool:
|
|
75
|
+
"""True if the array is well-behaved, i.e. it is aligned and writeable."""
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def c_contiguous(self) -> bool:
|
|
79
|
+
"""True if the array is C-contiguous."""
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def carray(self) -> bool:
|
|
83
|
+
"""True if the array is C-contiguous."""
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def contiguous(self) -> bool:
|
|
87
|
+
"""True if the array is contiguous."""
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def f_contiguous(self) -> bool:
|
|
91
|
+
"""True if the array is Fortran-contiguous."""
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def farray(self) -> bool:
|
|
95
|
+
"""True if the array is Fortran-contiguous."""
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def fnc(self) -> bool:
|
|
99
|
+
"""True if the array is Fortran-contiguous but not C-contiguous."""
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def forc(self) -> bool:
|
|
103
|
+
"""True if the array is C-contiguous or Fortran-contiguous."""
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def fortran(self) -> bool:
|
|
107
|
+
"""True if the array is Fortran-contiguous."""
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def owndata(self) -> bool:
|
|
111
|
+
"""True if the array owns its own data."""
|
|
112
|
+
|
|
113
|
+
def __getitem__(self, key: _GetFlag, /) -> bool:
|
|
114
|
+
"""Get the value of a flag."""
|
|
115
|
+
|
|
116
|
+
def __setitem__(self, key: _SetFlag, value: bool, /) -> None:
|
|
117
|
+
"""Set the value of a flag."""
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@runtime_checkable
|
|
121
|
+
class ArrayLikeWithoutSize[DT](Protocol):
|
|
122
|
+
"""Core array protocol shared by NumPy, JAX, and PyTorch.
|
|
123
|
+
|
|
124
|
+
This protocol omits ``size`` because PyTorch exposes it as a method rather than
|
|
125
|
+
the integer element-count property used by NumPy and JAX.
|
|
126
|
+
|
|
127
|
+
Unlike `numpy.typing.ArrayLike`, this protocol does not only check
|
|
128
|
+
whether an object can be converted to a numpy array, but rather
|
|
129
|
+
whether it supports core indexing and shape/metadata operations similar to a numpy array.
|
|
130
|
+
This protocol is based in the Python array API standard: https://data-apis.org/array-api
|
|
131
|
+
Unlike the official standard, this protocol does not require support for all numerical operations.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def __array__(self, dtype: DTypeLike | None = None, /, *, copy: bool | None = None) -> np.ndarray:
|
|
135
|
+
"""Convert to a numpy array."""
|
|
136
|
+
|
|
137
|
+
def __getitem__(self, index: ToIndices, /) -> Any: # noqa: ANN401
|
|
138
|
+
"""Return values selected by index."""
|
|
139
|
+
|
|
140
|
+
def __setitem__(self, index: ToIndices, value: NpArrayLike, /) -> None:
|
|
141
|
+
"""Set values selected by index."""
|
|
142
|
+
|
|
143
|
+
def __array_namespace__(self, /, *, api_version: ArrayAPIVersion | None = None) -> ModuleType:
|
|
144
|
+
"""Return the namespace of the array, e.g. 'numpy' or 'torch'."""
|
|
145
|
+
|
|
146
|
+
def to_device(self, device: Literal["cpu"], /, *, stream: int | Any | None = None) -> Self: # noqa: ANN401
|
|
147
|
+
"""Move the array to a device."""
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def ndim(self) -> int:
|
|
151
|
+
"""Number of dimensions."""
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def shape(self) -> tuple[int, ...]:
|
|
155
|
+
"""Shape of the array."""
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def dtype(self) -> Any: # noqa: ANN401
|
|
159
|
+
"""Data type of the array."""
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def device(self) -> Any: # noqa: ANN401
|
|
163
|
+
"""Device of the array."""
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def T(self) -> Any: # noqa: ANN401, N802
|
|
167
|
+
"""Transposed array."""
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def mT(self) -> Any: # noqa: ANN401, N802
|
|
171
|
+
"""Matrix transposed array."""
|
|
172
|
+
|
|
173
|
+
def __len__(self) -> int:
|
|
174
|
+
"""Length along the first axis."""
|
|
175
|
+
|
|
176
|
+
def __iter__(self) -> Iterator[Any]:
|
|
177
|
+
"""Iterator over the first axis."""
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@runtime_checkable
|
|
181
|
+
class ArrayLike[DT](ArrayLikeWithoutSize[DT], Protocol):
|
|
182
|
+
"""Core array protocol extended with an integer element-count property.
|
|
183
|
+
|
|
184
|
+
Unlike `numpy.typing.ArrayLike`, this protocol does not only check
|
|
185
|
+
whether an object can be converted to a numpy array, but rather
|
|
186
|
+
whether it supports core indexing and shape/metadata operations similar to a numpy array.
|
|
187
|
+
This protocol is based in the Python array API standard: https://data-apis.org/array-api
|
|
188
|
+
Unlike the official standard, this protocol does not require support for all numerical operations.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def size(self) -> int:
|
|
193
|
+
"""Number of elements in the array."""
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@runtime_checkable
|
|
197
|
+
class NumpyLikeConvertible[DT](ArrayLike[DT], Protocol):
|
|
198
|
+
"""Protocol for array-like objects that can be converted to numpy arrays."""
|
|
199
|
+
|
|
200
|
+
def __array_like__(self, dtype: DTypeLike | None = None, /, *, copy: bool | None = None) -> NumpyLike[Any]:
|
|
201
|
+
"""Convert to a NumpyLike."""
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@runtime_checkable
|
|
205
|
+
class NumpyLike[DT](ArrayLike[DT], Protocol):
|
|
206
|
+
"""Protocol for array-like objects that implement NumPy-specific APIs."""
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def dtype(self) -> np.dtype:
|
|
210
|
+
"""Data type of the array."""
|
|
211
|
+
|
|
212
|
+
def __array_ufunc__(self, ufunc: np.ufunc, method: str, *inputs: Any, **kwargs: Any) -> Any: # noqa: ANN401
|
|
213
|
+
"""Handle numpy ufuncs."""
|
|
214
|
+
|
|
215
|
+
def __array_function__(
|
|
216
|
+
self,
|
|
217
|
+
func: Callable,
|
|
218
|
+
types: tuple[type[Any], ...],
|
|
219
|
+
args: tuple[Any, ...],
|
|
220
|
+
kwargs: dict[str, Any],
|
|
221
|
+
) -> Any: # noqa: ANN401
|
|
222
|
+
"""Handle numpy array functions."""
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def mT(self) -> NumpyLike[Any] | np.ndarray: # noqa: N802
|
|
226
|
+
"""Matrix-transposed view."""
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def T(self) -> NumpyLike[Any] | np.ndarray: # noqa: N802
|
|
230
|
+
"""Transposed view."""
|
|
231
|
+
|
|
232
|
+
def transpose(self, *axes: int | None) -> NumpyLike[Any] | np.ndarray:
|
|
233
|
+
"""Return transposed array."""
|
|
234
|
+
|
|
235
|
+
def copy(self, order: Order = "C") -> NumpyLike[Any] | np.ndarray:
|
|
236
|
+
"""Return array copy."""
|
|
237
|
+
|
|
238
|
+
def astype(
|
|
239
|
+
self,
|
|
240
|
+
dtype: DTypeLike,
|
|
241
|
+
order: Order = "K",
|
|
242
|
+
casting: Literal["no", "equiv", "safe", "same_kind", "unsafe"] = "unsafe",
|
|
243
|
+
subok: bool = True,
|
|
244
|
+
copy: bool = True,
|
|
245
|
+
) -> NumpyLike[Any] | np.ndarray:
|
|
246
|
+
"""Return cast array."""
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def flags(self) -> ArrayFlagsLike:
|
|
250
|
+
"""The flags of the array."""
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class NumpyLikeImplementation[DT: NumpyLike | np.ndarray](ArrayLike[DT], np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|
254
|
+
"""ABC implementation for array-like objects that behave like NumPy arrays.
|
|
255
|
+
|
|
256
|
+
Representation-specific conversion and metadata are explicitly abstract rather
|
|
257
|
+
than inherited as protocol placeholders. Unsupported indexing, iteration and
|
|
258
|
+
scalar conversions retain their concrete ``NotImplementedError`` fallbacks.
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
@abstractmethod
|
|
262
|
+
def __array__(self, dtype: DTypeLike | None = None, /, *, copy: bool | None = None) -> np.ndarray:
|
|
263
|
+
"""Convert to a NumPy array."""
|
|
264
|
+
|
|
265
|
+
@abstractmethod
|
|
266
|
+
def __array_namespace__(self, /, *, api_version: ArrayAPIVersion | None = None) -> ModuleType:
|
|
267
|
+
"""Return the array namespace."""
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
@abstractmethod
|
|
271
|
+
def device(self) -> Any: # noqa: ANN401
|
|
272
|
+
"""Device of the array."""
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
@abstractmethod
|
|
276
|
+
def ndim(self) -> int:
|
|
277
|
+
"""Number of dimensions."""
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
@abstractmethod
|
|
281
|
+
def shape(self) -> tuple[int, ...]:
|
|
282
|
+
"""Shape of the array."""
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
@abstractmethod
|
|
286
|
+
def size(self) -> int:
|
|
287
|
+
"""Number of elements in the array."""
|
|
288
|
+
|
|
289
|
+
def __getitem__(self: Self, index: ToIndices, /) -> NumpyLikeImplementation[DT] | DT:
|
|
290
|
+
"""Return a new array containing the indexed values."""
|
|
291
|
+
msg = f"{type(self).__name__} does not support indexing."
|
|
292
|
+
raise NotImplementedError(msg)
|
|
293
|
+
|
|
294
|
+
def __setitem__(self, index: ToIndices, value: object, /) -> None:
|
|
295
|
+
"""Set the values at the given indices to the given value."""
|
|
296
|
+
msg = f"{type(self).__name__} does not support item assignment."
|
|
297
|
+
raise NotImplementedError(msg)
|
|
298
|
+
|
|
299
|
+
@override
|
|
300
|
+
@property
|
|
301
|
+
@abstractmethod
|
|
302
|
+
def dtype(self) -> np.dtype:
|
|
303
|
+
"""Data type of the array."""
|
|
304
|
+
|
|
305
|
+
@abstractmethod
|
|
306
|
+
def __array_ufunc__(self, ufunc: np.ufunc, method: str, *inputs: Any, **kwargs: Any) -> Any: # noqa: ANN401
|
|
307
|
+
"""Handle numpy ufuncs.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
ufunc: The ufunc to apply.
|
|
311
|
+
method: The method of the ufunc.
|
|
312
|
+
inputs: The input arrays.
|
|
313
|
+
kwargs: Additional keyword arguments.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
The result of applying the ufunc.
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
@abstractmethod
|
|
320
|
+
def __array_function__(
|
|
321
|
+
self,
|
|
322
|
+
func: Callable,
|
|
323
|
+
types: tuple[type[Any], ...],
|
|
324
|
+
args: tuple[Any, ...],
|
|
325
|
+
kwargs: dict[str, Any],
|
|
326
|
+
) -> Any: # noqa: ANN401
|
|
327
|
+
"""Handle numpy array functions.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
func: The numpy function to apply.
|
|
331
|
+
types: The types of the input arguments.
|
|
332
|
+
args: The input arguments.
|
|
333
|
+
kwargs: Additional keyword arguments.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
The result of applying the numpy function.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def mT(self) -> NumpyLike[Any] | np.ndarray: # noqa: N802
|
|
341
|
+
"""The transposed version of the underlying array."""
|
|
342
|
+
return np.matrix_transpose(self)
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
def T(self) -> NumpyLike[Any] | np.ndarray: # noqa: N802
|
|
346
|
+
"""The transposed version of the underlying array."""
|
|
347
|
+
return np.transpose(self)
|
|
348
|
+
|
|
349
|
+
def transpose(self, *axes: int | None) -> NumpyLike[Any] | np.ndarray:
|
|
350
|
+
"""Return a transposed version of the NumpySample.
|
|
351
|
+
|
|
352
|
+
This method implicitly also provides full axis tracking support for
|
|
353
|
+
- `np.moveaxis`
|
|
354
|
+
- `np.rollaxis`
|
|
355
|
+
Those functions call out to `transpose` methods for custom array types.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
axes: The axes to transpose.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
A transposed version of the NumpySample.
|
|
362
|
+
"""
|
|
363
|
+
if len(axes) == 0:
|
|
364
|
+
return np.transpose(self)
|
|
365
|
+
if len(axes) == 1 and not isinstance(axes[0], int):
|
|
366
|
+
return np.transpose(self, axes[0])
|
|
367
|
+
return np.transpose(self, axes) # ty:ignore[no-matching-overload]
|
|
368
|
+
|
|
369
|
+
def copy(self, order: Order = "C") -> NumpyLike[Any] | np.ndarray:
|
|
370
|
+
"""Return a copy of the array."""
|
|
371
|
+
return np.copy(self, order=order, subok=True) # ty:ignore[no-matching-overload]
|
|
372
|
+
|
|
373
|
+
def astype(
|
|
374
|
+
self,
|
|
375
|
+
dtype: DTypeLike,
|
|
376
|
+
order: Order = "K",
|
|
377
|
+
casting: Literal["no", "equiv", "safe", "same_kind", "unsafe"] = "unsafe",
|
|
378
|
+
subok: bool = True,
|
|
379
|
+
copy: bool = True,
|
|
380
|
+
) -> NumpyLike[Any] | np.ndarray:
|
|
381
|
+
"""Copy of the array, cast to a specified type."""
|
|
382
|
+
if order != "K" or casting != "unsafe" or not subok:
|
|
383
|
+
msg = "Non-default ndarray.astype options require a representation-specific implementation."
|
|
384
|
+
raise NotImplementedError(msg)
|
|
385
|
+
return np.astype(self, dtype, copy=copy) # ty: ignore[no-matching-overload]
|
|
386
|
+
|
|
387
|
+
def __index__(self) -> int:
|
|
388
|
+
"""Converts 0d integer array to a Python integer."""
|
|
389
|
+
msg = f"{type(self).__name__} does not support conversion to index."
|
|
390
|
+
raise NotImplementedError(msg)
|
|
391
|
+
|
|
392
|
+
def __int__(self) -> int:
|
|
393
|
+
"""Converts 0d integer array to a Python integer."""
|
|
394
|
+
msg = f"{type(self).__name__} does not support conversion to int."
|
|
395
|
+
raise NotImplementedError(msg)
|
|
396
|
+
|
|
397
|
+
def __bool__(self) -> bool:
|
|
398
|
+
"""Converts 0d boolean array to a Python boolean."""
|
|
399
|
+
msg = f"{type(self).__name__} does not support conversion to bool."
|
|
400
|
+
raise NotImplementedError(msg)
|
|
401
|
+
|
|
402
|
+
def __float__(self) -> float:
|
|
403
|
+
"""Converts 0d float array to a Python float."""
|
|
404
|
+
msg = f"{type(self).__name__} does not support conversion to float."
|
|
405
|
+
raise NotImplementedError(msg)
|
|
406
|
+
|
|
407
|
+
def __complex__(self) -> complex:
|
|
408
|
+
"""Converts 0d complex array to a Python complex."""
|
|
409
|
+
msg = f"{type(self).__name__} does not support conversion to complex."
|
|
410
|
+
raise NotImplementedError(msg)
|
|
411
|
+
|
|
412
|
+
def __len__(self) -> int:
|
|
413
|
+
"""Returns the length of the array along the first dimension.
|
|
414
|
+
|
|
415
|
+
This is not mandated by the array API standard, but numpy, torch and jax all support it.
|
|
416
|
+
"""
|
|
417
|
+
msg = f"{type(self).__name__} does not support len()."
|
|
418
|
+
raise NotImplementedError(msg)
|
|
419
|
+
|
|
420
|
+
def __iter__(self) -> Iterator[DT]:
|
|
421
|
+
"""Returns an iterator over the first dimension of the array.
|
|
422
|
+
|
|
423
|
+
This is not mandated by the array API standard, but numpy, torch and jax all support it.
|
|
424
|
+
"""
|
|
425
|
+
msg = f"{type(self).__name__} does not support iteration."
|
|
426
|
+
raise NotImplementedError(msg)
|
|
427
|
+
|
|
428
|
+
@property
|
|
429
|
+
@abstractmethod
|
|
430
|
+
def flags(self) -> ArrayFlagsLike:
|
|
431
|
+
"""The flags of the array."""
|
|
432
|
+
|
|
433
|
+
@override
|
|
434
|
+
def to_device(self, device: Any, /, *, stream: Any = None) -> Self:
|
|
435
|
+
"""Move the array to a device."""
|
|
436
|
+
return self
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
NumpyLikeImplementation.register(np.ndarray)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
@flexdispatch
|
|
443
|
+
def to_numpy_like[DT](
|
|
444
|
+
array: object,
|
|
445
|
+
*,
|
|
446
|
+
dtype: DTypeLike | None = None,
|
|
447
|
+
copy: bool | None = None,
|
|
448
|
+
) -> NumpyLike[Any] | np.ndarray:
|
|
449
|
+
"""Convert an ArrayLike to a NumpyLike.
|
|
450
|
+
|
|
451
|
+
If possible, use the __array_like__ method to convert the array, otherwise use np.asanyarray.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
array: The array to convert.
|
|
455
|
+
dtype: The desired data type of the output array.
|
|
456
|
+
copy: Whether to return a copy of the input array.
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
The converted array.
|
|
460
|
+
"""
|
|
461
|
+
return np.asanyarray(array, dtype=dtype, copy=copy)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@to_numpy_like.register(NumpyLikeConvertible)
|
|
465
|
+
def _to_numpy_like_convertible(
|
|
466
|
+
array: NumpyLikeConvertible,
|
|
467
|
+
*,
|
|
468
|
+
dtype: DTypeLike | None = None,
|
|
469
|
+
copy: bool | None = None,
|
|
470
|
+
) -> NumpyLike[Any] | NDArray[Any]:
|
|
471
|
+
return array.__array_like__(dtype, copy=copy)
|