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.
@@ -1,4 +1,4 @@
1
- __version__ = "0.1.1"
1
+ __version__ = "0.1.3"
2
2
  from ._main import (
3
3
  InconsistentNdimError,
4
4
  InconsistentNdimErrorMultipleSolutions,
@@ -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: tuple[SubscriptInfoFromSubcriptItem, ...]
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: tuple[SubscriptInfoFromShapeItemUnique, ...]
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
- pass
80
+ shapes: Sequence[int] | None
81
+ ndims: Sequence[int]
82
+ reason: str
81
83
 
82
84
 
83
85
  class InconsistentNdimErrorMultipleSolutions(InconsistentNdimError):
84
- def __init__(self, reason: str, *args: object) -> None:
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
- *args,
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__(self, reason: str, *args: object) -> None:
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
- *args,
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
- SubscriptInfoFromSubcript(all=((i, j), (*k, *l), (*l, i)), unique=(i, j, *k, *l))
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: set[SubscriptInfoFromSubcriptItem] = {
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 = [subscript for subscript in info.unique if subscript.is_variable]
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
- name_to_ndim = defaultdict(lambda: 1, parse_variable_ndim(subscripts, ndims))
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
- (i:3, j:4, *k:(5,), *l:(6, 7))
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, unique=tuple(sorted(info_unique, key=lambda x: x.name))
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.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
- (i:3, j:4, *k:(5,), *l:(6, 7))
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,,