array-api-shape-check 1.0.0__tar.gz → 1.0.1__tar.gz
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-1.0.0/src/array_api_shape_check.egg-info → array_api_shape_check-1.0.1}/PKG-INFO +1 -1
- {array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/pyproject.toml +1 -1
- {array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/src/array_api_shape_check/_main.py +35 -16
- {array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1/src/array_api_shape_check.egg-info}/PKG-INFO +1 -1
- array_api_shape_check-1.0.1/tests/test_main.py +43 -0
- array_api_shape_check-1.0.0/tests/test_main.py +0 -11
- {array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/LICENSE +0 -0
- {array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/README.md +0 -0
- {array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/setup.cfg +0 -0
- {array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/src/array_api_shape_check/__init__.py +0 -0
- {array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/src/array_api_shape_check/py.typed +0 -0
- {array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/src/array_api_shape_check.egg-info/SOURCES.txt +0 -0
- {array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/src/array_api_shape_check.egg-info/dependency_links.txt +0 -0
- {array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/src/array_api_shape_check.egg-info/requires.txt +0 -0
- {array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/src/array_api_shape_check.egg-info/top_level.txt +0 -0
{array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/src/array_api_shape_check/_main.py
RENAMED
|
@@ -39,36 +39,46 @@ class SubscriptInfoFromShapeItemUnchecked(SubscriptInfoFromSubcriptItem):
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
@attrs.frozen(kw_only=True)
|
|
42
|
-
class SubscriptInfoFromShapeItemUnique(
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
class SubscriptInfoFromShapeItemUnique[BroadcastShape: tuple[int, ...] | None](
|
|
43
|
+
SubscriptInfoFromSubcriptItem
|
|
44
|
+
):
|
|
45
|
+
shape_broadcasted: BroadcastShape
|
|
46
|
+
"""The broadcast shape, or None for an inconsistent subscript."""
|
|
45
47
|
|
|
46
48
|
def __repr__(self) -> str:
|
|
47
49
|
if self.is_variable:
|
|
48
50
|
return f"*{self.name}:{self.shape_broadcasted}"
|
|
49
|
-
|
|
51
|
+
if self.shape_broadcasted is None:
|
|
52
|
+
return f"{self.name}:None"
|
|
53
|
+
return f"{self.name}:{cast('tuple[int, ...]', self.shape_broadcasted)[0]}"
|
|
50
54
|
|
|
51
55
|
|
|
52
56
|
@attrs.frozen(kw_only=True)
|
|
53
|
-
class SubscriptInfoFromShapeItem(
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
class SubscriptInfoFromShapeItem[BroadcastShape: tuple[int, ...] | None](
|
|
58
|
+
SubscriptInfoFromShapeItemUnchecked
|
|
59
|
+
):
|
|
60
|
+
shape_broadcasted: BroadcastShape
|
|
61
|
+
"""The broadcast shape, or None for an inconsistent subscript."""
|
|
56
62
|
|
|
57
63
|
def __repr__(self) -> str:
|
|
64
|
+
if self.shape_broadcasted is None:
|
|
65
|
+
current = self.shape_current if self.is_variable else self.shape_current[0]
|
|
66
|
+
return f"{'*' if self.is_variable else ''}{self.name}:{current}->None"
|
|
58
67
|
if self.is_variable:
|
|
59
68
|
if self.shape_current != self.shape_broadcasted:
|
|
60
69
|
return f"*{self.name}:{self.shape_current}->{self.shape_broadcasted}"
|
|
61
70
|
return f"*{self.name}:{self.shape_current}"
|
|
62
71
|
if self.shape_current != self.shape_broadcasted:
|
|
63
|
-
|
|
72
|
+
broadcasted = cast("tuple[int, ...]", self.shape_broadcasted)
|
|
73
|
+
return f"{self.name}:{self.shape_current[0]}->{broadcasted[0]}"
|
|
64
74
|
return f"{self.name}:{self.shape_current[0]}"
|
|
65
75
|
|
|
66
76
|
|
|
67
77
|
@attrs.frozen(kw_only=True)
|
|
68
|
-
class SubscriptInfoFromShape:
|
|
69
|
-
all: tuple[tuple[SubscriptInfoFromShapeItem, ...], ...]
|
|
78
|
+
class SubscriptInfoFromShape[BroadcastShape: tuple[int, ...] | None]:
|
|
79
|
+
all: tuple[tuple[SubscriptInfoFromShapeItem[BroadcastShape], ...], ...]
|
|
70
80
|
"""The subscript info grouped by order, then by operand"""
|
|
71
|
-
unique: dict[str, SubscriptInfoFromShapeItemUnique]
|
|
81
|
+
unique: dict[str, SubscriptInfoFromShapeItemUnique[BroadcastShape]]
|
|
72
82
|
"""The unique subscripts,
|
|
73
83
|
ignoring order and shapes not broadcasted,
|
|
74
84
|
sorted lexicographically by name"""
|
|
@@ -111,9 +121,14 @@ class InconsistentNdimErrorNoSolutions(InconsistentNdimError): # noqa: N818
|
|
|
111
121
|
|
|
112
122
|
|
|
113
123
|
class InconsistentShapeError(ValueError):
|
|
114
|
-
subscript_info: SubscriptInfoFromShape
|
|
124
|
+
subscript_info: SubscriptInfoFromShape[tuple[int, ...] | None]
|
|
115
125
|
|
|
116
|
-
def __init__(
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
subscript_info: SubscriptInfoFromShape[tuple[int, ...] | None],
|
|
129
|
+
reason: str,
|
|
130
|
+
*args: object,
|
|
131
|
+
) -> None:
|
|
117
132
|
super().__init__("Inconsistent shapes: " + str(subscript_info.all) + f"\n {reason}", *args)
|
|
118
133
|
self.subscript_info = subscript_info
|
|
119
134
|
|
|
@@ -344,7 +359,7 @@ def _parse_shapes(
|
|
|
344
359
|
|
|
345
360
|
def check_shapes(
|
|
346
361
|
subscripts: str, /, *operands: Array | tuple[int | None, ...], names: str | None = None
|
|
347
|
-
) -> SubscriptInfoFromShape:
|
|
362
|
+
) -> SubscriptInfoFromShape[tuple[int, ...]]:
|
|
348
363
|
"""
|
|
349
364
|
Parse variable subscript ndims by solving linear equations.
|
|
350
365
|
|
|
@@ -435,12 +450,15 @@ def check_shapes(
|
|
|
435
450
|
info_flatten_keyed = [(i, item) for i, info_array in enumerate(info_all) for item in info_array]
|
|
436
451
|
errors = []
|
|
437
452
|
shape_broadcasted = {}
|
|
438
|
-
for key, group in groupby(
|
|
453
|
+
for key, group in groupby(
|
|
454
|
+
sorted(info_flatten_keyed, key=lambda x: x[1].name), key=lambda x: x[1].name
|
|
455
|
+
):
|
|
439
456
|
group_list = list(group)
|
|
440
457
|
shapes = [item.shape_current for _, item in group_list]
|
|
441
458
|
try:
|
|
442
459
|
shape_broadcasted[key] = np.broadcast_shapes(*shapes)
|
|
443
460
|
except ValueError:
|
|
461
|
+
shape_broadcasted[key] = None
|
|
444
462
|
inner_text = ", ".join([f"{shape} ({names_[i]})" for i, shape in group_list])
|
|
445
463
|
errors.append(f"Key {key}: shapes {inner_text} are not broadcastable")
|
|
446
464
|
|
|
@@ -475,4 +493,5 @@ def check_shapes(
|
|
|
475
493
|
if errors:
|
|
476
494
|
raise InconsistentShapeError(result, "\n".join(errors))
|
|
477
495
|
|
|
478
|
-
|
|
496
|
+
# No broadcast shape can be None unless an inconsistency was recorded.
|
|
497
|
+
return cast("SubscriptInfoFromShape[tuple[int, ...]]", result)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from array_api_shape_check import InconsistentShapeError, check_shapes
|
|
4
|
+
from array_api_shape_check._main import _get_operand_names # noqa: PLC2701
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_get_operand_names():
|
|
8
|
+
def inner(a, b) -> list[str | None]:
|
|
9
|
+
return _get_operand_names(a, b)
|
|
10
|
+
|
|
11
|
+
c = 1
|
|
12
|
+
assert inner(c, 4 * 4)[0] == "c"
|
|
13
|
+
# assert inner(c, 4 * 4)[1] is not None
|
|
14
|
+
# assert "@py_assert" in repr(inner(c, 4 * 4)[1])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.parametrize(
|
|
18
|
+
("subscripts", "shapes", "key"),
|
|
19
|
+
[
|
|
20
|
+
("i,i", ((3,), (4,)), "i"),
|
|
21
|
+
("ij,ji", ((3, 4), (4, 9)), "i"),
|
|
22
|
+
("*BM,*BN", ((2, 3), (4, 3)), "B"),
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
def test_incompatible_shapes(
|
|
26
|
+
subscripts: str, shapes: tuple[tuple[int, ...], ...], key: str
|
|
27
|
+
) -> None:
|
|
28
|
+
with pytest.raises(InconsistentShapeError, match=f"Key {key}") as exc:
|
|
29
|
+
check_shapes(subscripts, *shapes, names="first,second")
|
|
30
|
+
assert exc.value.subscript_info.unique[key].shape_broadcasted is None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_broadcasts_nonconsecutive_subscripts() -> None:
|
|
34
|
+
info = check_shapes("ij,ji", (1, 4), (4, 9), names="first,second")
|
|
35
|
+
assert info.unique["i"].shape_broadcasted == (9,)
|
|
36
|
+
assert info.unique["j"].shape_broadcasted == (4,)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_reports_all_inconsistent_subscripts() -> None:
|
|
40
|
+
with pytest.raises(InconsistentShapeError) as exc:
|
|
41
|
+
check_shapes("ij,ij", (2, 3), (4, 5), names="first,second")
|
|
42
|
+
assert "Key i" in str(exc.value)
|
|
43
|
+
assert "Key j" in str(exc.value)
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
from array_api_shape_check._main import _get_operand_names # noqa: PLC2701
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def test_get_operand_names():
|
|
5
|
-
def inner(a, b) -> list[str | None]:
|
|
6
|
-
return _get_operand_names(a, b)
|
|
7
|
-
|
|
8
|
-
c = 1
|
|
9
|
-
assert inner(c, 4 * 4)[0] == "c"
|
|
10
|
-
# assert inner(c, 4 * 4)[1] is not None
|
|
11
|
-
# assert "@py_assert" in repr(inner(c, 4 * 4)[1])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/src/array_api_shape_check/__init__.py
RENAMED
|
File without changes
|
{array_api_shape_check-1.0.0 → array_api_shape_check-1.0.1}/src/array_api_shape_check/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|