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.
@@ -1,4 +1,4 @@
1
- __version__ = "0.1.2"
1
+ __version__ = "0.1.3"
2
2
  from ._main import (
3
3
  InconsistentNdimError,
4
4
  InconsistentNdimErrorMultipleSolutions,
@@ -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
- else:
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
- else:
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
- else:
64
- if self.shape_current != self.shape_broadcasted:
65
- return f"{self.name}:{self.shape_current[0]}->{self.shape_broadcasted[0]}"
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
- pass
81
-
82
-
83
- class InconsistentNdimErrorMultipleSolutions(InconsistentNdimError):
84
- def __init__(self, reason: str, *args: object) -> None:
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
- *args,
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__(self, reason: str, *args: object) -> None:
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
- *args,
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
- shape = operand
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,) # type: ignore
321
+ shapes += (shape,)
310
322
  del operands
311
323
  ndims = [len(shape) for shape in shapes]
312
- name_to_ndim = defaultdict(lambda: 1, parse_variable_ndim(subscripts, ndims))
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.get(item.name), # type: ignore
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.get(item.name), # type: ignore
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.1.2
3
+ Version: 1.0.0
4
4
  Summary: Check shapes of input arrays easily
5
- Author-email: 34j <34j.95a2p@simplelogin.com>
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (82.0.1)
2
+ Generator: setuptools (84.2.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,