array-api-shape-check 0.1.1__py3-none-any.whl → 0.1.3__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 +46 -28
- {array_api_shape_check-0.1.1.dist-info → array_api_shape_check-0.1.3.dist-info}/METADATA +2 -2
- array_api_shape_check-0.1.3.dist-info/RECORD +8 -0
- array_api_shape_check-0.1.1.dist-info/RECORD +0 -8
- {array_api_shape_check-0.1.1.dist-info → array_api_shape_check-0.1.3.dist-info}/WHEEL +0 -0
- {array_api_shape_check-0.1.1.dist-info → array_api_shape_check-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {array_api_shape_check-0.1.1.dist-info → array_api_shape_check-0.1.3.dist-info}/top_level.txt +0 -0
array_api_shape_check/_main.py
CHANGED
|
@@ -23,7 +23,7 @@ class SubscriptInfoFromSubcriptItem:
|
|
|
23
23
|
@attrs.frozen(kw_only=True)
|
|
24
24
|
class SubscriptInfoFromSubcript:
|
|
25
25
|
all: tuple[tuple[SubscriptInfoFromSubcriptItem, ...], ...]
|
|
26
|
-
unique:
|
|
26
|
+
unique: dict[str, SubscriptInfoFromSubcriptItem]
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@attrs.frozen(kw_only=True)
|
|
@@ -70,33 +70,45 @@ class SubscriptInfoFromShapeItem(SubscriptInfoFromShapeItemUnchecked):
|
|
|
70
70
|
class SubscriptInfoFromShape:
|
|
71
71
|
all: tuple[tuple[SubscriptInfoFromShapeItem, ...], ...]
|
|
72
72
|
"""The subscript info grouped by order, then by operand"""
|
|
73
|
-
unique:
|
|
73
|
+
unique: dict[str, SubscriptInfoFromShapeItemUnique]
|
|
74
74
|
"""The unique subscripts,
|
|
75
75
|
ignoring order and shapes not broadcasted,
|
|
76
76
|
sorted lexicographically by name"""
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
class InconsistentNdimError(ValueError):
|
|
80
|
-
|
|
80
|
+
shapes: Sequence[int] | None
|
|
81
|
+
ndims: Sequence[int]
|
|
82
|
+
reason: str
|
|
81
83
|
|
|
82
84
|
|
|
83
85
|
class InconsistentNdimErrorMultipleSolutions(InconsistentNdimError):
|
|
84
|
-
def __init__(
|
|
86
|
+
def __init__(
|
|
87
|
+
self, ndims: Sequence[int], reason: str, shapes: Sequence[int] | None = None
|
|
88
|
+
) -> None:
|
|
89
|
+
self.ndims = ndims
|
|
90
|
+
self.reason = reason
|
|
91
|
+
self.shapes = shapes
|
|
85
92
|
super().__init__(
|
|
86
93
|
f"Inconsistent ndims: there are multiple possible solutions "
|
|
87
94
|
"to determine the number of dimensions "
|
|
88
|
-
f"for variable subscripts [{reason}]"
|
|
89
|
-
|
|
95
|
+
f"for variable subscripts [{reason}]"
|
|
96
|
+
f"\nndims: {ndims}" + (f"\nshapes: {shapes}" if shapes is not None else "")
|
|
90
97
|
)
|
|
91
98
|
|
|
92
99
|
|
|
93
100
|
class InconsistentNdimErrorNoSolutions(InconsistentNdimError):
|
|
94
|
-
def __init__(
|
|
101
|
+
def __init__(
|
|
102
|
+
self, ndims: Sequence[int], reason: str, shapes: Sequence[int] | None = None
|
|
103
|
+
) -> None:
|
|
104
|
+
self.ndims = ndims
|
|
105
|
+
self.reason = reason
|
|
106
|
+
self.shapes = shapes
|
|
95
107
|
super().__init__(
|
|
96
108
|
f"Inconsistent ndims: there are no solution "
|
|
97
109
|
f"to determine the number of dimensions "
|
|
98
|
-
f"for variable subscripts [{reason}]"
|
|
99
|
-
|
|
110
|
+
f"for variable subscripts [{reason}]"
|
|
111
|
+
f"\nndims: {ndims}" + (f"\nshapes: {shapes}" if shapes is not None else "")
|
|
100
112
|
)
|
|
101
113
|
|
|
102
114
|
|
|
@@ -157,8 +169,11 @@ def parse_subscripts(subscripts: str, /) -> SubscriptInfoFromSubcript:
|
|
|
157
169
|
|
|
158
170
|
Examples
|
|
159
171
|
--------
|
|
160
|
-
>>> parse_subscripts("ij,*k*l,*li")
|
|
161
|
-
|
|
172
|
+
>>> info = parse_subscripts("ij,*k*l,*li")
|
|
173
|
+
>>> info.all
|
|
174
|
+
((i, j), (*k, *l), (*l, i))
|
|
175
|
+
>>> info.unique
|
|
176
|
+
{'i': i, 'j': j, 'k': *k, 'l': *l}
|
|
162
177
|
|
|
163
178
|
"""
|
|
164
179
|
# If . other than ...
|
|
@@ -181,9 +196,9 @@ def parse_subscripts(subscripts: str, /) -> SubscriptInfoFromSubcript:
|
|
|
181
196
|
is_variable = False
|
|
182
197
|
info_all += (info_array,)
|
|
183
198
|
|
|
184
|
-
info_unique
|
|
185
|
-
x for info_array in info_all for x in info_array
|
|
186
|
-
|
|
199
|
+
info_unique = tuple(
|
|
200
|
+
sorted({x for info_array in info_all for x in info_array}, key=lambda x: x.name)
|
|
201
|
+
)
|
|
187
202
|
# raise if there are subscripts with same name but different is_variable
|
|
188
203
|
names = set()
|
|
189
204
|
for info in info_unique:
|
|
@@ -193,9 +208,7 @@ def parse_subscripts(subscripts: str, /) -> SubscriptInfoFromSubcript:
|
|
|
193
208
|
)
|
|
194
209
|
names.add(info.name)
|
|
195
210
|
|
|
196
|
-
return SubscriptInfoFromSubcript(
|
|
197
|
-
all=info_all, unique=tuple(sorted(info_unique, key=lambda x: x.name))
|
|
198
|
-
)
|
|
211
|
+
return SubscriptInfoFromSubcript(all=info_all, unique={item.name: item for item in info_unique})
|
|
199
212
|
|
|
200
213
|
|
|
201
214
|
def parse_variable_ndim(subscripts: str, ndims: Sequence[int], /) -> dict[str, int]:
|
|
@@ -253,11 +266,12 @@ def parse_variable_ndim(subscripts: str, ndims: Sequence[int], /) -> dict[str, i
|
|
|
253
266
|
)
|
|
254
267
|
|
|
255
268
|
# decide dimensions by solving linear equations
|
|
256
|
-
info_variable_unique = [
|
|
269
|
+
info_variable_unique = [
|
|
270
|
+
subscript for subscript in info.unique.values() if subscript.is_variable
|
|
271
|
+
]
|
|
257
272
|
if len(info_variable_unique) > len(info.all):
|
|
258
|
-
raise InconsistentNdimErrorMultipleSolutions("number of variables")
|
|
273
|
+
raise InconsistentNdimErrorMultipleSolutions(ndims, "number of variables")
|
|
259
274
|
rhs = np.asarray(ndims, dtype=int)
|
|
260
|
-
del ndims
|
|
261
275
|
mat = np.zeros((len(info.all), len(info_variable_unique)), dtype=int)
|
|
262
276
|
for i, info_array in enumerate(info.all):
|
|
263
277
|
for subscript in info_array:
|
|
@@ -270,19 +284,19 @@ def parse_variable_ndim(subscripts: str, ndims: Sequence[int], /) -> dict[str, i
|
|
|
270
284
|
# solve overdetermined linear equations using least squares method
|
|
271
285
|
variable_dims, residuals, rank, _singular_values = np.linalg.lstsq(mat, rhs, rcond=None)
|
|
272
286
|
if rank != len(info_variable_unique):
|
|
273
|
-
raise InconsistentNdimErrorMultipleSolutions("rank")
|
|
287
|
+
raise InconsistentNdimErrorMultipleSolutions(ndims, "rank")
|
|
274
288
|
if residuals.size > 0 and not np.isclose(residuals[0], 0):
|
|
275
|
-
raise InconsistentNdimErrorNoSolutions("residuals")
|
|
289
|
+
raise InconsistentNdimErrorNoSolutions(ndims, "residuals")
|
|
276
290
|
variable_dims = np.round(variable_dims).astype(int)
|
|
277
291
|
if np.any(variable_dims < 0):
|
|
278
|
-
raise InconsistentNdimErrorNoSolutions("negative")
|
|
292
|
+
raise InconsistentNdimErrorNoSolutions(ndims, "negative")
|
|
279
293
|
return {
|
|
280
294
|
subscript.name: int(variable_dims[j]) for j, subscript in enumerate(info_variable_unique)
|
|
281
295
|
}
|
|
282
296
|
|
|
283
297
|
|
|
284
298
|
def _parse_shapes(
|
|
285
|
-
subscripts: str, /, *operands: Array | tuple[int, ...]
|
|
299
|
+
subscripts: str, /, *operands: Array | tuple[int | None, ...]
|
|
286
300
|
) -> tuple[tuple[SubscriptInfoFromShapeItemUnchecked, ...], ...]:
|
|
287
301
|
info = parse_subscripts(subscripts)
|
|
288
302
|
if len(info.all) != len(operands):
|
|
@@ -306,7 +320,10 @@ def _parse_shapes(
|
|
|
306
320
|
shapes += (shape,) # type: ignore
|
|
307
321
|
del operands
|
|
308
322
|
ndims = [len(shape) for shape in shapes]
|
|
309
|
-
|
|
323
|
+
try:
|
|
324
|
+
name_to_ndim = defaultdict(lambda: 1, parse_variable_ndim(subscripts, ndims))
|
|
325
|
+
except InconsistentNdimError as e:
|
|
326
|
+
raise type(e)(e.ndims, e.reason, shapes) from e
|
|
310
327
|
del subscripts, ndims
|
|
311
328
|
info_all: tuple[tuple[SubscriptInfoFromShapeItemUnchecked, ...], ...] = ()
|
|
312
329
|
for info_array, shape in zip(info.all, shapes, strict=False):
|
|
@@ -325,7 +342,7 @@ def _parse_shapes(
|
|
|
325
342
|
|
|
326
343
|
|
|
327
344
|
def check_shapes(
|
|
328
|
-
subscripts: str, /, *operands: Array | tuple[int, ...], names: str | None = None
|
|
345
|
+
subscripts: str, /, *operands: Array | tuple[int | None, ...], names: str | None = None
|
|
329
346
|
) -> SubscriptInfoFromShape:
|
|
330
347
|
"""
|
|
331
348
|
Parse variable subscript ndims by solving linear equations.
|
|
@@ -363,7 +380,7 @@ def check_shapes(
|
|
|
363
380
|
>>> info.all
|
|
364
381
|
((i:1->3, j:4), (*k:(5,), *l:(6, 7)), (*l:(1, 7)->(6, 7), i:3))
|
|
365
382
|
>>> info.unique
|
|
366
|
-
|
|
383
|
+
{'i': i:3, 'j': j:4, 'k': *k:(5,), 'l': *l:(6, 7)}
|
|
367
384
|
|
|
368
385
|
Internally `check_shapes()` calls `parse_variable_ndim()`,
|
|
369
386
|
which determines the number of dimensions for variable subscripts by least squares.
|
|
@@ -448,7 +465,8 @@ def check_shapes(
|
|
|
448
465
|
for item in info_array
|
|
449
466
|
}
|
|
450
467
|
result = SubscriptInfoFromShape(
|
|
451
|
-
all=info_all_new,
|
|
468
|
+
all=info_all_new,
|
|
469
|
+
unique={item.name: item for item in sorted(info_unique, key=lambda x: x.name)},
|
|
452
470
|
)
|
|
453
471
|
|
|
454
472
|
if errors:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: array-api-shape-check
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Check shapes of input arrays easily
|
|
5
5
|
Author-email: 34j <34j.95a2p@simplelogin.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -82,7 +82,7 @@ Install this via pip (or your favourite package manager):
|
|
|
82
82
|
>>> info.all
|
|
83
83
|
((i:1->3, j:4), (*k:(5,), *l:(6, 7)), (*l:(1, 7)->(6, 7), i:3))
|
|
84
84
|
>>> info.unique
|
|
85
|
-
|
|
85
|
+
{'i': i:3, 'j': j:4, 'k': *k:(5,), 'l': *l:(6, 7)}
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
Internally `check_shapes()` calls `parse_variable_ndim()`, which determines the number of dimensions for variable subscripts by least squares. If this is successful, checks if each subscript is consistent, then finnaly raises error for all inconsistencies at once.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
array_api_shape_check/__init__.py,sha256=8i9ZV_x6HXtVd0g7N3ec190lQ1LUC2s93A9p-J3C9WI,940
|
|
2
|
+
array_api_shape_check/_main.py,sha256=LqwDK6OD9ldcUaXmmbG5aBXzOW0gEJuu1iAe9rs2BiY,16652
|
|
3
|
+
array_api_shape_check/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
array_api_shape_check-0.1.3.dist-info/licenses/LICENSE,sha256=Otwb96xcK4QBqXXVdBKpLZVnc4mJuFTa8rsMUEpphMM,1061
|
|
5
|
+
array_api_shape_check-0.1.3.dist-info/METADATA,sha256=of_gXs9JcFp-b0KQwa9Sbf8GIXPvChmU5VGfvBWyYOQ,6213
|
|
6
|
+
array_api_shape_check-0.1.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
+
array_api_shape_check-0.1.3.dist-info/top_level.txt,sha256=pirw2DeaWnDU_rSg3CZ1MsE-tJWEvndYSQG0illDMkI,22
|
|
8
|
+
array_api_shape_check-0.1.3.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
array_api_shape_check/__init__.py,sha256=MsV4O9FLovSeq30_Y1MyxZH_RYayQ01KaT7YCRSKx9U,940
|
|
2
|
-
array_api_shape_check/_main.py,sha256=0D9MY4rPLxQgc-bEm4I4ROi-xGL9jrHOnNSy3uF70C8,15947
|
|
3
|
-
array_api_shape_check/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
array_api_shape_check-0.1.1.dist-info/licenses/LICENSE,sha256=Otwb96xcK4QBqXXVdBKpLZVnc4mJuFTa8rsMUEpphMM,1061
|
|
5
|
-
array_api_shape_check-0.1.1.dist-info/METADATA,sha256=iT7COus2b1y0GkqIw6fHI9wVP59e7mVuhi2wONPCatQ,6193
|
|
6
|
-
array_api_shape_check-0.1.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
-
array_api_shape_check-0.1.1.dist-info/top_level.txt,sha256=pirw2DeaWnDU_rSg3CZ1MsE-tJWEvndYSQG0illDMkI,22
|
|
8
|
-
array_api_shape_check-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
{array_api_shape_check-0.1.1.dist-info → array_api_shape_check-0.1.3.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{array_api_shape_check-0.1.1.dist-info → array_api_shape_check-0.1.3.dist-info}/top_level.txt
RENAMED
|
File without changes
|