batch-tensorsolve 0.1.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.
@@ -0,0 +1,4 @@
1
+ __version__ = "0.1.0"
2
+ from ._main import AmbiguousBatchAxesWarning, broadcast_without_repeating, btensorsolve
3
+
4
+ __all__ = ["AmbiguousBatchAxesWarning", "broadcast_without_repeating", "btensorsolve"]
@@ -0,0 +1,178 @@
1
+ import warnings
2
+ from typing import TypeVar
3
+
4
+ from array_api_compat import array_namespace
5
+
6
+ TArray = TypeVar("TArray")
7
+
8
+
9
+ class AmbiguousBatchAxesWarning(RuntimeWarning):
10
+ pass
11
+
12
+
13
+ def broadcast_without_repeating(
14
+ *arrays: TArray, check_same_ndim: bool = False
15
+ ) -> tuple[TArray, ...]:
16
+ """
17
+ Broadcast arrays without repeating the data.
18
+
19
+ Parameters
20
+ ----------
21
+ arrays : TArray
22
+ The arrays to broadcast.
23
+ check_same_ndim : bool, optional
24
+ Whether to check if all arrays have the same number of dimensions.
25
+ Default is False.
26
+
27
+ Returns
28
+ -------
29
+ tuple[TArray]
30
+ The broadcasted arrays.
31
+
32
+ """
33
+ xp = array_namespace(*arrays)
34
+ arrays_ = tuple(xp.asarray(a) for a in arrays)
35
+ xp.broadcast_shapes(*[a.shape for a in arrays_])
36
+ if check_same_ndim:
37
+ if len({a.ndim for a in arrays_}) != 1:
38
+ raise ValueError(
39
+ "All arrays must have the same number of dimensions, "
40
+ f"but got {tuple(a.ndim for a in arrays_)}"
41
+ )
42
+ return arrays_
43
+ max_dim = max(a.ndim for a in arrays_)
44
+ return tuple(array[(None,) * (max_dim - array.ndim) + (...,)] for array in arrays_)
45
+
46
+
47
+ def btensorsolve(
48
+ a: TArray,
49
+ b: TArray,
50
+ /,
51
+ *,
52
+ axes: tuple[int] | None = None,
53
+ num_batch_axes: int | None = None,
54
+ ) -> TArray:
55
+ """
56
+ Solve the tensor equation ``a x = b`` for x.
57
+
58
+ It is assumed that all indices of `x` are summed over in the product,
59
+ together with the rightmost indices of `a`, as is done in, for example,
60
+ ``tensordot(a, x, axes=x.ndim)``.
61
+
62
+ Parameters
63
+ ----------
64
+ a : array_like
65
+ Coefficient tensor, of shape ``b.shape + Q``. `Q`, a tuple, equals
66
+ the shape of that sub-tensor of `a` consisting of the appropriate
67
+ number of its rightmost indices, and must be such that
68
+ **there exists i that**
69
+ ``prod(Q) == prod(b.shape[i:])``
70
+ b : array_like
71
+ Right-hand tensor, which can be of any shape.
72
+ axes : tuple of ints, optional
73
+ Axes in `a` to reorder to the right, before inversion.
74
+ If None (default), no reordering is done.
75
+ num_batch_axes : int, optional
76
+ The number of batch dimensions. If None (default), the number of batch
77
+ dimensions is inferred from the shapes of `a` and `b`.
78
+
79
+ Let ``shape = np.broadcast_shapes(a.shape[:b.ndim],
80
+ b.shape) + a.shape[b.ndim:]``.
81
+
82
+ It is recommended to specify this argument, as the inference
83
+ might be wrong if there exists i >= 0, j > 0 that
84
+ ``prod(shape[:i]) == prod(shape[i+j:])``. (j + 1 possibilities)
85
+
86
+ For example, if `a` has shape (3, 1, 1, 2, 2) and `b` has shape
87
+ (3, 1, 1, 2), it is possible that
88
+ - axis 1 is the batch axes and desired output shape is (3, 2)
89
+ - axis 1, 2 are the batch axes and desired output shape is (3, 1, 2)
90
+ - axis 1, 2, 3 are the batch axes and desired output shape is (3, 1, 1, 2)
91
+
92
+ Returns
93
+ -------
94
+ x : ndarray, shape Q
95
+
96
+ Raises
97
+ ------
98
+ LinAlgError
99
+ If `a` is singular or not 'square' (in the above sense).
100
+
101
+ Warnings
102
+ --------
103
+ AmbiguousBatchAxesWarning
104
+ If the number of batch axes cannot be inferred from the shapes of
105
+ `a` and `b`, and `num_batch_axes` is not specified.
106
+
107
+ See Also
108
+ --------
109
+ numpy.tensordot, tensorinv, numpy.einsum
110
+
111
+ Examples
112
+ --------
113
+ >>> import numpy as np
114
+ >>> rng = np.random.default_rng()
115
+ >>> a = rng.normal(size=(2, 2*3, 4, 2, 3, 4))
116
+ >>> b = rng.normal(size=(2, 2*3, 4))
117
+ >>> x = np.linalg.tensorsolve(a, b)
118
+ >>> x.shape
119
+ (2, 2, 3, 4)
120
+ >>> np.allclose(np.einsum('...ijklm,...klm->...ij', a, x), b)
121
+ True
122
+
123
+ """
124
+ # https://github.com/numpy/numpy/blob/
125
+ # e7a123b2d3eca9897843791dd698c1803d9a39c2/numpy/linalg/_linalg.py#L291
126
+ xp = array_namespace(a, b)
127
+ a_ = xp.asarray(a)
128
+ b_ = xp.asarray(b)
129
+ an = a_.ndim
130
+ if axes is not None:
131
+ allaxes = list(range(0, an))
132
+ for k in axes:
133
+ allaxes.remove(k)
134
+ allaxes.insert(an, k)
135
+ a_ = a_.transpose(allaxes)
136
+
137
+ # find right dimensions
138
+ # a = [2 (dim1) 2 2 3 (dim2) 2 6]
139
+ # b = [2 (dim1) 2 2 3 (dim2)]
140
+ axis_sol_last = b_.ndim
141
+ ashape = a_.shape
142
+ bshape = b_.shape
143
+ shape = xp.broadcast_shapes(ashape[:axis_sol_last], bshape)
144
+
145
+ # the dimension of the linear system
146
+ sol_size = int(xp.prod(ashape[axis_sol_last:]))
147
+ if num_batch_axes is None:
148
+ sol_size_current = 1
149
+ for num_batch_axes in range(axis_sol_last - 1, -1, -1):
150
+ sol_size_current *= shape[num_batch_axes]
151
+ if sol_size_current == sol_size:
152
+ break
153
+ else:
154
+ raise ValueError(
155
+ "Unable to divide batch dimensions and solution dimensions"
156
+ )
157
+
158
+ if num_batch_axes > 0 and shape[num_batch_axes - 1] == 1:
159
+ warnings.warn(
160
+ "It is impossible to infer the number of "
161
+ "batch axes from the shapes of `a` and `b`. "
162
+ "Consider specifying `num_batch_axes` explicitly.",
163
+ AmbiguousBatchAxesWarning,
164
+ stacklevel=2,
165
+ )
166
+
167
+ a_ = xp.broadcast_to(
168
+ a_,
169
+ ashape[:num_batch_axes]
170
+ + shape[num_batch_axes:axis_sol_last]
171
+ + ashape[axis_sol_last:],
172
+ )
173
+ b_ = xp.broadcast_to(b_, bshape[:num_batch_axes] + shape[num_batch_axes:])
174
+ a_ = a_.reshape(ashape[:num_batch_axes] + (sol_size, sol_size))
175
+ b_ = b_.reshape(bshape[:num_batch_axes] + (sol_size, 1))
176
+ x = xp.linalg.solve(a_, b_)
177
+ x = x.reshape(shape[:num_batch_axes] + ashape[axis_sol_last:])
178
+ return x
File without changes
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: batch-tensorsolve
3
+ Version: 0.1.0
4
+ Summary: Batched tensorsolve() for NumPy / PyTorch / JAX
5
+ Author-email: 34j <34j.95a2p@simplelogin.com>
6
+ License: MIT
7
+ Project-URL: Bug Tracker, https://github.com/34j/batch-tensorsolve/issues
8
+ Project-URL: Changelog, https://github.com/34j/batch-tensorsolve/blob/main/CHANGELOG.md
9
+ Project-URL: documentation, https://batch-tensorsolve.readthedocs.io
10
+ Project-URL: repository, https://github.com/34j/batch-tensorsolve
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Natural Language :: English
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: array-api-compat>=1.11.2
24
+ Requires-Dist: numpy>=2.2.5
25
+ Dynamic: license-file
26
+
27
+ # Batch Tensorsolve
28
+
29
+ <p align="center">
30
+ <a href="https://github.com/34j/batch-tensorsolve/actions/workflows/ci.yml?query=branch%3Amain">
31
+ <img src="https://img.shields.io/github/actions/workflow/status/34j/batch-tensorsolve/ci.yml?branch=main&label=CI&logo=github&style=flat-square" alt="CI Status" >
32
+ </a>
33
+ <a href="https://batch-tensorsolve.readthedocs.io">
34
+ <img src="https://img.shields.io/readthedocs/batch-tensorsolve.svg?logo=read-the-docs&logoColor=fff&style=flat-square" alt="Documentation Status">
35
+ </a>
36
+ <a href="https://codecov.io/gh/34j/batch-tensorsolve">
37
+ <img src="https://img.shields.io/codecov/c/github/34j/batch-tensorsolve.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">
38
+ </a>
39
+ </p>
40
+ <p align="center">
41
+ <a href="https://github.com/astral-sh/uv">
42
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json" alt="uv">
43
+ </a>
44
+ <a href="https://github.com/astral-sh/ruff">
45
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff">
46
+ </a>
47
+ <a href="https://github.com/pre-commit/pre-commit">
48
+ <img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" alt="pre-commit">
49
+ </a>
50
+ </p>
51
+ <p align="center">
52
+ <a href="https://pypi.org/project/batch-tensorsolve/">
53
+ <img src="https://img.shields.io/pypi/v/batch-tensorsolve.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
54
+ </a>
55
+ <img src="https://img.shields.io/pypi/pyversions/batch-tensorsolve.svg?style=flat-square&logo=python&amp;logoColor=fff" alt="Supported Python versions">
56
+ <img src="https://img.shields.io/pypi/l/batch-tensorsolve.svg?style=flat-square" alt="License">
57
+ </p>
58
+
59
+ ---
60
+
61
+ **Documentation**: <a href="https://batch-tensorsolve.readthedocs.io" target="_blank">https://batch-tensorsolve.readthedocs.io </a>
62
+
63
+ **Source Code**: <a href="https://github.com/34j/batch-tensorsolve" target="_blank">https://github.com/34j/batch-tensorsolve </a>
64
+
65
+ ---
66
+
67
+ Batched tensorsolve() for NumPy / PyTorch / JAX. ([numpy/numpy#28099](https://github.com/numpy/numpy/issues/28099))
68
+
69
+ ## Installation
70
+
71
+ Install this via pip (or your favourite package manager):
72
+
73
+ ```shell
74
+ pip install batch-tensorsolve
75
+ ```
76
+
77
+ ## Usage
78
+
79
+ ```python
80
+ import numpy as np
81
+ from numpy.testing import assert_allclose
82
+
83
+ from batch_tensorsolve import btensorsolve
84
+
85
+ a = np.random.randn(2, 2, 3, 6)
86
+ b = np.random.randn(2, 2, 3)
87
+ assert_allclose(np.einsum("...ijk,...k->...ij", a, btensorsolve(a, b)), b)
88
+ ```
89
+
90
+ ## Advanced Usage
91
+
92
+ It is recommended to explicitly specify the batch axes, as the desired shape will be ambiguous if axes of size 1 are present.
93
+
94
+ ```python
95
+ import numpy as np
96
+
97
+ from batch_tensorsolve import btensorsolve
98
+
99
+ a = np.random.randn(2, 1, 2, 2)
100
+ b = np.random.randn(2, 1, 2)
101
+ # 2 possibilities:
102
+ assert btensorsolve(a, b, num_batch_axes=1).shape == (2, 2) # 1st axis is batch
103
+ assert btensorsolve(a, b, num_batch_axes=2).shape == (2, 1, 2) # 1st and 2nd axes are batch
104
+ ```
105
+
106
+ Broadcasting-like behavior is also supported:
107
+
108
+ ```python
109
+ import numpy as np
110
+ from numpy.testing import assert_allclose
111
+
112
+ from batch_tensorsolve import btensorsolve
113
+
114
+ a = np.random.randn(1, 2, 3, 6) # -> (2, 2, 3, 6)
115
+ b = np.random.randn(2, 1, 1) # -> (2, 2, 3)
116
+ left = np.einsum("...ijk,...k->...ij", a, btensorsolve(a, b))
117
+ assert_allclose(left, np.broadcast_to(b, left.shape))
118
+ ```
119
+
120
+ Note that broadcasting (repeating) `a` for non-batch axes will result in `numpy.linalg.LinAlgError: Singular matrix` because the matrix representation of `a` has duplicate rows.
121
+
122
+ ## Contributors ✨
123
+
124
+ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
125
+
126
+ <!-- prettier-ignore-start -->
127
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
128
+ <!-- markdownlint-disable -->
129
+ <!-- markdownlint-enable -->
130
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
131
+ <!-- prettier-ignore-end -->
132
+
133
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
134
+
135
+ ## Credits
136
+
137
+ [![Copier](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/copier-org/copier/master/img/badge/badge-grayscale-inverted-border-orange.json)](https://github.com/copier-org/copier)
138
+
139
+ This package was created with
140
+ [Copier](https://copier.readthedocs.io/) and the
141
+ [browniebroke/pypackage-template](https://github.com/browniebroke/pypackage-template)
142
+ project template.
@@ -0,0 +1,8 @@
1
+ batch_tensorsolve/__init__.py,sha256=9ggnkEF1DPkJqGEZERfUnpjmWS8sU6QBSbRk79Lvbdg,198
2
+ batch_tensorsolve/_main.py,sha256=u7pypXQumqMXq_mANq3gxGGO0iPQp54zQ6mnW1gYOIs,5677
3
+ batch_tensorsolve/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ batch_tensorsolve-0.1.0.dist-info/licenses/LICENSE,sha256=Otwb96xcK4QBqXXVdBKpLZVnc4mJuFTa8rsMUEpphMM,1061
5
+ batch_tensorsolve-0.1.0.dist-info/METADATA,sha256=HX5cZa2JiQOeVEYBeHrGDsk8lG1o5h5QnCptoxSeABM,5631
6
+ batch_tensorsolve-0.1.0.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
7
+ batch_tensorsolve-0.1.0.dist-info/top_level.txt,sha256=C2-1orVxMMM7moCnIWJ35hk_aPGfh7P1ulR1hExyaUU,18
8
+ batch_tensorsolve-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.4.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,22 @@
1
+
2
+ MIT License
3
+
4
+ Copyright (c) 2025 34j
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ batch_tensorsolve