array-api-shape-check 0.1.2__py3-none-any.whl → 1.0.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.
- array_api_shape_check/__init__.py +1 -1
- array_api_shape_check/_main.py +54 -37
- {array_api_shape_check-0.1.2.dist-info → array_api_shape_check-1.0.0.dist-info}/METADATA +2 -2
- array_api_shape_check-1.0.0.dist-info/RECORD +8 -0
- {array_api_shape_check-0.1.2.dist-info → array_api_shape_check-1.0.0.dist-info}/WHEEL +1 -1
- array_api_shape_check-0.1.2.dist-info/RECORD +0 -8
- {array_api_shape_check-0.1.2.dist-info → array_api_shape_check-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {array_api_shape_check-0.1.2.dist-info → array_api_shape_check-1.0.0.dist-info}/top_level.txt +0 -0
array_api_shape_check/_main.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import inspect
|
|
1
2
|
import re
|
|
2
3
|
from collections import defaultdict
|
|
3
4
|
from collections.abc import Sequence
|
|
4
5
|
from itertools import groupby
|
|
5
|
-
from typing import Any
|
|
6
|
+
from typing import Any, cast
|
|
6
7
|
|
|
7
8
|
import attrs
|
|
8
9
|
import numpy as np
|
|
@@ -34,8 +35,7 @@ class SubscriptInfoFromShapeItemUnchecked(SubscriptInfoFromSubcriptItem):
|
|
|
34
35
|
def __repr__(self) -> str:
|
|
35
36
|
if self.is_variable:
|
|
36
37
|
return f"*{self.name}:{self.shape_current}"
|
|
37
|
-
|
|
38
|
-
return f"{self.name}:{self.shape_current[0]}"
|
|
38
|
+
return f"{self.name}:{self.shape_current[0]}"
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
@attrs.frozen(kw_only=True)
|
|
@@ -46,8 +46,7 @@ class SubscriptInfoFromShapeItemUnique(SubscriptInfoFromSubcriptItem):
|
|
|
46
46
|
def __repr__(self) -> str:
|
|
47
47
|
if self.is_variable:
|
|
48
48
|
return f"*{self.name}:{self.shape_broadcasted}"
|
|
49
|
-
|
|
50
|
-
return f"{self.name}:{self.shape_broadcasted[0]}"
|
|
49
|
+
return f"{self.name}:{self.shape_broadcasted[0]}"
|
|
51
50
|
|
|
52
51
|
|
|
53
52
|
@attrs.frozen(kw_only=True)
|
|
@@ -60,10 +59,9 @@ class SubscriptInfoFromShapeItem(SubscriptInfoFromShapeItemUnchecked):
|
|
|
60
59
|
if self.shape_current != self.shape_broadcasted:
|
|
61
60
|
return f"*{self.name}:{self.shape_current}->{self.shape_broadcasted}"
|
|
62
61
|
return f"*{self.name}:{self.shape_current}"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return f"{self.name}:{self.shape_current[0]}"
|
|
62
|
+
if self.shape_current != self.shape_broadcasted:
|
|
63
|
+
return f"{self.name}:{self.shape_current[0]}->{self.shape_broadcasted[0]}"
|
|
64
|
+
return f"{self.name}:{self.shape_current[0]}"
|
|
67
65
|
|
|
68
66
|
|
|
69
67
|
@attrs.frozen(kw_only=True)
|
|
@@ -77,26 +75,38 @@ class SubscriptInfoFromShape:
|
|
|
77
75
|
|
|
78
76
|
|
|
79
77
|
class InconsistentNdimError(ValueError):
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
shapes: Sequence[int] | None
|
|
79
|
+
ndims: Sequence[int]
|
|
80
|
+
reason: str
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class InconsistentNdimErrorMultipleSolutions(InconsistentNdimError): # noqa: N818
|
|
84
|
+
def __init__(
|
|
85
|
+
self, ndims: Sequence[int], reason: str, shapes: Sequence[int] | None = None
|
|
86
|
+
) -> None:
|
|
87
|
+
self.ndims = ndims
|
|
88
|
+
self.reason = reason
|
|
89
|
+
self.shapes = shapes
|
|
85
90
|
super().__init__(
|
|
86
91
|
f"Inconsistent ndims: there are multiple possible solutions "
|
|
87
92
|
"to determine the number of dimensions "
|
|
88
|
-
f"for variable subscripts [{reason}]"
|
|
89
|
-
|
|
93
|
+
f"for variable subscripts [{reason}]"
|
|
94
|
+
f"\nndims: {ndims}" + (f"\nshapes: {shapes}" if shapes is not None else "")
|
|
90
95
|
)
|
|
91
96
|
|
|
92
97
|
|
|
93
|
-
class InconsistentNdimErrorNoSolutions(InconsistentNdimError):
|
|
94
|
-
def __init__(
|
|
98
|
+
class InconsistentNdimErrorNoSolutions(InconsistentNdimError): # noqa: N818
|
|
99
|
+
def __init__(
|
|
100
|
+
self, ndims: Sequence[int], reason: str, shapes: Sequence[int] | None = None
|
|
101
|
+
) -> None:
|
|
102
|
+
self.ndims = ndims
|
|
103
|
+
self.reason = reason
|
|
104
|
+
self.shapes = shapes
|
|
95
105
|
super().__init__(
|
|
96
106
|
f"Inconsistent ndims: there are no solution "
|
|
97
107
|
f"to determine the number of dimensions "
|
|
98
|
-
f"for variable subscripts [{reason}]"
|
|
99
|
-
|
|
108
|
+
f"for variable subscripts [{reason}]"
|
|
109
|
+
f"\nndims: {ndims}" + (f"\nshapes: {shapes}" if shapes is not None else "")
|
|
100
110
|
)
|
|
101
111
|
|
|
102
112
|
|
|
@@ -109,8 +119,6 @@ class InconsistentShapeError(ValueError):
|
|
|
109
119
|
|
|
110
120
|
|
|
111
121
|
def _get_operand_names(*operands: Any) -> list[str | None]:
|
|
112
|
-
import inspect
|
|
113
|
-
|
|
114
122
|
frame = inspect.currentframe()
|
|
115
123
|
if frame is None:
|
|
116
124
|
return [None] * len(operands)
|
|
@@ -224,6 +232,10 @@ def parse_variable_ndim(subscripts: str, ndims: Sequence[int], /) -> dict[str, i
|
|
|
224
232
|
------
|
|
225
233
|
ValueError
|
|
226
234
|
If the subscript is invalid.
|
|
235
|
+
InconsistentNdimErrorMultipleSolutions
|
|
236
|
+
If the variable dimensions cannot be uniquely determined.
|
|
237
|
+
InconsistentNdimErrorNoSolutions
|
|
238
|
+
If the variable dimensions have no valid solution.
|
|
227
239
|
|
|
228
240
|
Examples
|
|
229
241
|
--------
|
|
@@ -258,9 +270,8 @@ def parse_variable_ndim(subscripts: str, ndims: Sequence[int], /) -> dict[str, i
|
|
|
258
270
|
subscript for subscript in info.unique.values() if subscript.is_variable
|
|
259
271
|
]
|
|
260
272
|
if len(info_variable_unique) > len(info.all):
|
|
261
|
-
raise InconsistentNdimErrorMultipleSolutions("number of variables")
|
|
273
|
+
raise InconsistentNdimErrorMultipleSolutions(ndims, "number of variables")
|
|
262
274
|
rhs = np.asarray(ndims, dtype=int)
|
|
263
|
-
del ndims
|
|
264
275
|
mat = np.zeros((len(info.all), len(info_variable_unique)), dtype=int)
|
|
265
276
|
for i, info_array in enumerate(info.all):
|
|
266
277
|
for subscript in info_array:
|
|
@@ -273,19 +284,19 @@ def parse_variable_ndim(subscripts: str, ndims: Sequence[int], /) -> dict[str, i
|
|
|
273
284
|
# solve overdetermined linear equations using least squares method
|
|
274
285
|
variable_dims, residuals, rank, _singular_values = np.linalg.lstsq(mat, rhs, rcond=None)
|
|
275
286
|
if rank != len(info_variable_unique):
|
|
276
|
-
raise InconsistentNdimErrorMultipleSolutions("rank")
|
|
287
|
+
raise InconsistentNdimErrorMultipleSolutions(ndims, "rank")
|
|
277
288
|
if residuals.size > 0 and not np.isclose(residuals[0], 0):
|
|
278
|
-
raise InconsistentNdimErrorNoSolutions("residuals")
|
|
289
|
+
raise InconsistentNdimErrorNoSolutions(ndims, "residuals")
|
|
279
290
|
variable_dims = np.round(variable_dims).astype(int)
|
|
280
291
|
if np.any(variable_dims < 0):
|
|
281
|
-
raise InconsistentNdimErrorNoSolutions("negative")
|
|
292
|
+
raise InconsistentNdimErrorNoSolutions(ndims, "negative")
|
|
282
293
|
return {
|
|
283
294
|
subscript.name: int(variable_dims[j]) for j, subscript in enumerate(info_variable_unique)
|
|
284
295
|
}
|
|
285
296
|
|
|
286
297
|
|
|
287
298
|
def _parse_shapes(
|
|
288
|
-
subscripts: str, /, *operands: Array | tuple[int, ...]
|
|
299
|
+
subscripts: str, /, *operands: Array | tuple[int | None, ...]
|
|
289
300
|
) -> tuple[tuple[SubscriptInfoFromShapeItemUnchecked, ...], ...]:
|
|
290
301
|
info = parse_subscripts(subscripts)
|
|
291
302
|
if len(info.all) != len(operands):
|
|
@@ -295,21 +306,25 @@ def _parse_shapes(
|
|
|
295
306
|
)
|
|
296
307
|
shapes: tuple[tuple[int, ...], ...] = ()
|
|
297
308
|
for operand in operands:
|
|
298
|
-
shape: tuple[int, ...] # type: ignore
|
|
299
309
|
if isinstance(operand, tuple):
|
|
300
|
-
|
|
310
|
+
if None in operand:
|
|
311
|
+
raise ValueError("Operand shape cannot contain None")
|
|
312
|
+
shape = cast("tuple[int, ...]", operand)
|
|
301
313
|
elif hasattr(operand, "shape"):
|
|
302
314
|
if None in operand.shape:
|
|
303
315
|
raise ValueError("Operand shape cannot contain None")
|
|
304
|
-
shape = operand.shape
|
|
316
|
+
shape = cast("tuple[int, ...]", operand.shape)
|
|
305
317
|
else:
|
|
306
318
|
raise TypeError(
|
|
307
319
|
f"Invalid operand: expected an array or a shape tuple, but got {type(operand)}"
|
|
308
320
|
)
|
|
309
|
-
shapes += (shape,)
|
|
321
|
+
shapes += (shape,)
|
|
310
322
|
del operands
|
|
311
323
|
ndims = [len(shape) for shape in shapes]
|
|
312
|
-
|
|
324
|
+
try:
|
|
325
|
+
name_to_ndim = defaultdict(lambda: 1, parse_variable_ndim(subscripts, ndims))
|
|
326
|
+
except InconsistentNdimError as e:
|
|
327
|
+
raise type(e)(e.ndims, e.reason, shapes) from e
|
|
313
328
|
del subscripts, ndims
|
|
314
329
|
info_all: tuple[tuple[SubscriptInfoFromShapeItemUnchecked, ...], ...] = ()
|
|
315
330
|
for info_array, shape in zip(info.all, shapes, strict=False):
|
|
@@ -322,13 +337,13 @@ def _parse_shapes(
|
|
|
322
337
|
shape_current=shape[: name_to_ndim[item.name]],
|
|
323
338
|
),
|
|
324
339
|
)
|
|
325
|
-
shape = shape[name_to_ndim[item.name] :]
|
|
340
|
+
shape = shape[name_to_ndim[item.name] :] # noqa: PLW2901
|
|
326
341
|
info_all += (info_array_new,)
|
|
327
342
|
return info_all
|
|
328
343
|
|
|
329
344
|
|
|
330
345
|
def check_shapes(
|
|
331
|
-
subscripts: str, /, *operands: Array | tuple[int, ...], names: str | None = None
|
|
346
|
+
subscripts: str, /, *operands: Array | tuple[int | None, ...], names: str | None = None
|
|
332
347
|
) -> SubscriptInfoFromShape:
|
|
333
348
|
"""
|
|
334
349
|
Parse variable subscript ndims by solving linear equations.
|
|
@@ -359,6 +374,8 @@ def check_shapes(
|
|
|
359
374
|
------
|
|
360
375
|
ValueError
|
|
361
376
|
If the subscript is invalid.
|
|
377
|
+
InconsistentShapeError
|
|
378
|
+
If the operand shapes are inconsistent.
|
|
362
379
|
|
|
363
380
|
Examples
|
|
364
381
|
--------
|
|
@@ -436,7 +453,7 @@ def check_shapes(
|
|
|
436
453
|
name=item.name,
|
|
437
454
|
is_variable=item.is_variable,
|
|
438
455
|
shape_current=item.shape_current,
|
|
439
|
-
shape_broadcasted=shape_broadcasted
|
|
456
|
+
shape_broadcasted=shape_broadcasted[item.name],
|
|
440
457
|
),
|
|
441
458
|
)
|
|
442
459
|
info_all_new += (info_array_new,)
|
|
@@ -445,7 +462,7 @@ def check_shapes(
|
|
|
445
462
|
SubscriptInfoFromShapeItemUnique(
|
|
446
463
|
name=item.name,
|
|
447
464
|
is_variable=item.is_variable,
|
|
448
|
-
shape_broadcasted=shape_broadcasted
|
|
465
|
+
shape_broadcasted=shape_broadcasted[item.name],
|
|
449
466
|
)
|
|
450
467
|
for info_array in info_all_new
|
|
451
468
|
for item in info_array
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: array-api-shape-check
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Check shapes of input arrays easily
|
|
5
|
-
Author
|
|
5
|
+
Author: 34j
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Bug Tracker, https://github.com/34j/array-api-shape-check/issues
|
|
8
8
|
Project-URL: Changelog, https://github.com/34j/array-api-shape-check/blob/main/CHANGELOG.md
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
array_api_shape_check/__init__.py,sha256=8i9ZV_x6HXtVd0g7N3ec190lQ1LUC2s93A9p-J3C9WI,940
|
|
2
|
+
array_api_shape_check/_main.py,sha256=VXDxm8ds6mjq9qiJ4BFNWJWXxjBZKMXZ7WafYvuICt4,16965
|
|
3
|
+
array_api_shape_check/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
array_api_shape_check-1.0.0.dist-info/licenses/LICENSE,sha256=Otwb96xcK4QBqXXVdBKpLZVnc4mJuFTa8rsMUEpphMM,1061
|
|
5
|
+
array_api_shape_check-1.0.0.dist-info/METADATA,sha256=6cj-qjtOx_MV44WBn5-mLIY2XnChx71cQNG8V_74jQY,6179
|
|
6
|
+
array_api_shape_check-1.0.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
7
|
+
array_api_shape_check-1.0.0.dist-info/top_level.txt,sha256=pirw2DeaWnDU_rSg3CZ1MsE-tJWEvndYSQG0illDMkI,22
|
|
8
|
+
array_api_shape_check-1.0.0.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
array_api_shape_check/__init__.py,sha256=peDt2aytKiIHNe9K5YRB8VJG15wxDIt0bKm7Z6Wu8s8,940
|
|
2
|
-
array_api_shape_check/_main.py,sha256=kqGml_xsXX1CtieBZDyyd_m-_kBqe-F-oj4PVpfKsek,16029
|
|
3
|
-
array_api_shape_check/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
array_api_shape_check-0.1.2.dist-info/licenses/LICENSE,sha256=Otwb96xcK4QBqXXVdBKpLZVnc4mJuFTa8rsMUEpphMM,1061
|
|
5
|
-
array_api_shape_check-0.1.2.dist-info/METADATA,sha256=bQKTAYo0XcoLyASUyChwsJ0RdNwlb-CBN5oHQrGkgV4,6213
|
|
6
|
-
array_api_shape_check-0.1.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
-
array_api_shape_check-0.1.2.dist-info/top_level.txt,sha256=pirw2DeaWnDU_rSg3CZ1MsE-tJWEvndYSQG0illDMkI,22
|
|
8
|
-
array_api_shape_check-0.1.2.dist-info/RECORD,,
|
{array_api_shape_check-0.1.2.dist-info → array_api_shape_check-1.0.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{array_api_shape_check-0.1.2.dist-info → array_api_shape_check-1.0.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|