hiepy 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.
- hiepy/__init__.py +117 -0
- hiepy/algebra/__init__.py +0 -0
- hiepy/algebra/add.py +271 -0
- hiepy/algebra/contract.py +101 -0
- hiepy/algebra/ele_mode_mul.py +113 -0
- hiepy/algebra/ele_mul.py +324 -0
- hiepy/algebra/mode_mul.py +104 -0
- hiepy/algebra/scalar_mul.py +85 -0
- hiepy/algebra/sub.py +118 -0
- hiepy/factory/__init__.py +0 -0
- hiepy/factory/arange.py +105 -0
- hiepy/factory/constant.py +107 -0
- hiepy/factory/copy.py +231 -0
- hiepy/factory/hilbert.py +109 -0
- hiepy/factory/ones.py +102 -0
- hiepy/factory/randn.py +305 -0
- hiepy/factory/to_htt.py +158 -0
- hiepy/factory/zeros.py +84 -0
- hiepy/httensor/__init__.py +0 -0
- hiepy/httensor/full.py +75 -0
- hiepy/httensor/get_item.py +190 -0
- hiepy/httensor/getter.py +262 -0
- hiepy/httensor/gramians.py +212 -0
- hiepy/httensor/httensor.py +283 -0
- hiepy/httensor/io.py +83 -0
- hiepy/linalg/__init__.py +0 -0
- hiepy/linalg/diagonal_tensor.py +68 -0
- hiepy/linalg/left_svd_gramian.py +86 -0
- hiepy/linalg/local_left_svd.py +128 -0
- hiepy/linalg/matricize.py +160 -0
- hiepy/linalg/mode_mul_torch.py +298 -0
- hiepy/linalg/norm.py +46 -0
- hiepy/linalg/vectorize.py +98 -0
- hiepy/py.typed +0 -0
- hiepy/transformation/__init__.py +0 -0
- hiepy/transformation/orthog.py +106 -0
- hiepy/transformation/squeeze.py +162 -0
- hiepy/transformation/truncate.py +362 -0
- hiepy/utils/__init__.py +0 -0
- hiepy/utils/checks.py +58 -0
- hiepy/utils/data_types.py +59 -0
- hiepy/utils/dimtree.py +334 -0
- hiepy/utils/dtype_device.py +87 -0
- hiepy/utils/trunc_rank.py +89 -0
- hiepy-0.1.0.dist-info/METADATA +91 -0
- hiepy-0.1.0.dist-info/RECORD +49 -0
- hiepy-0.1.0.dist-info/WHEEL +5 -0
- hiepy-0.1.0.dist-info/licenses/LICENSE +156 -0
- hiepy-0.1.0.dist-info/top_level.txt +1 -0
hiepy/__init__.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
hiepy - Hierarchical Tucker (HT) tensor library in PyTorch.
|
|
3
|
+
|
|
4
|
+
Overview
|
|
5
|
+
--------
|
|
6
|
+
hiepy provides efficient representations and operations for tensors in the
|
|
7
|
+
Hierarchical Tucker format, including algebraic operations, truncation,
|
|
8
|
+
and conversions from dense PyTorch tensors.
|
|
9
|
+
|
|
10
|
+
Main Component
|
|
11
|
+
---------------
|
|
12
|
+
HTTensor
|
|
13
|
+
Hierarchical Tucker tensor representation.
|
|
14
|
+
|
|
15
|
+
Key Features
|
|
16
|
+
-----------
|
|
17
|
+
- Efficient tensor arithmetic (addition, contraction, etc.)
|
|
18
|
+
- Rank truncation and orthogonalization utilities
|
|
19
|
+
- Conversions from dense PyTorch tensors
|
|
20
|
+
- Random tensor generation in HT format
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from importlib.metadata import version as _version
|
|
24
|
+
|
|
25
|
+
from .algebra.add import add
|
|
26
|
+
from .algebra.contract import contract
|
|
27
|
+
from .algebra.ele_mode_mul import ele_mode_mul
|
|
28
|
+
from .algebra.ele_mul import ele_mul
|
|
29
|
+
from .algebra.mode_mul import mode_mul
|
|
30
|
+
from .algebra.scalar_mul import scalar_mul
|
|
31
|
+
from .algebra.sub import sub
|
|
32
|
+
from .factory.arange import arange
|
|
33
|
+
from .factory.constant import constant
|
|
34
|
+
from .factory.copy import clone, copy, detach, requires_grad_, to
|
|
35
|
+
from .factory.hilbert import hilbert
|
|
36
|
+
from .factory.ones import ones
|
|
37
|
+
from .factory.randn import randn
|
|
38
|
+
from .factory.to_htt import to_htt
|
|
39
|
+
from .factory.zeros import zeros
|
|
40
|
+
from .httensor.full import full
|
|
41
|
+
from .httensor.get_item import get
|
|
42
|
+
from .httensor.getter import device, dtype, nbytes, order, rank, requires_grad, shape
|
|
43
|
+
from .httensor.gramians import gramians
|
|
44
|
+
from .httensor.httensor import HTTensor
|
|
45
|
+
from .httensor.io import load, save
|
|
46
|
+
from .linalg.left_svd_gramian import left_svd_gramian
|
|
47
|
+
from .linalg.local_left_svd import local_left_svd
|
|
48
|
+
from .linalg.matricize import dematricize, matricize
|
|
49
|
+
from .linalg.mode_mul_torch import mode_mul_torch, multi_mode_mul_torch
|
|
50
|
+
from .linalg.norm import norm
|
|
51
|
+
from .linalg.vectorize import devectorize, vectorize
|
|
52
|
+
from .transformation.orthog import orthog
|
|
53
|
+
from .transformation.squeeze import squeeze
|
|
54
|
+
from .transformation.truncate import truncate, truncate_gram, truncate_svd
|
|
55
|
+
from .utils.data_types import ScalarType, Shape, TensorDict, TruncOpts
|
|
56
|
+
from .utils.dimtree import DimTree, Node
|
|
57
|
+
from .utils.trunc_rank import trunc_rank
|
|
58
|
+
|
|
59
|
+
# Version of the installed package, from the single source in pyproject.toml
|
|
60
|
+
__version__ = _version("hiepy")
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
"__version__",
|
|
64
|
+
"nbytes",
|
|
65
|
+
"dtype",
|
|
66
|
+
"device",
|
|
67
|
+
"requires_grad",
|
|
68
|
+
"order",
|
|
69
|
+
"rank",
|
|
70
|
+
"shape",
|
|
71
|
+
"gramians",
|
|
72
|
+
"HTTensor",
|
|
73
|
+
"full",
|
|
74
|
+
"get",
|
|
75
|
+
"load",
|
|
76
|
+
"save",
|
|
77
|
+
"truncate",
|
|
78
|
+
"truncate_gram",
|
|
79
|
+
"truncate_svd",
|
|
80
|
+
"orthog",
|
|
81
|
+
"squeeze",
|
|
82
|
+
"left_svd_gramian",
|
|
83
|
+
"local_left_svd",
|
|
84
|
+
"norm",
|
|
85
|
+
"TruncOpts",
|
|
86
|
+
"ScalarType",
|
|
87
|
+
"Shape",
|
|
88
|
+
"TensorDict",
|
|
89
|
+
"trunc_rank",
|
|
90
|
+
"DimTree",
|
|
91
|
+
"Node",
|
|
92
|
+
"matricize",
|
|
93
|
+
"dematricize",
|
|
94
|
+
"vectorize",
|
|
95
|
+
"devectorize",
|
|
96
|
+
"add",
|
|
97
|
+
"sub",
|
|
98
|
+
"mode_mul_torch",
|
|
99
|
+
"multi_mode_mul_torch",
|
|
100
|
+
"contract",
|
|
101
|
+
"ele_mul",
|
|
102
|
+
"ele_mode_mul",
|
|
103
|
+
"mode_mul",
|
|
104
|
+
"scalar_mul",
|
|
105
|
+
"zeros",
|
|
106
|
+
"hilbert",
|
|
107
|
+
"ones",
|
|
108
|
+
"randn",
|
|
109
|
+
"arange",
|
|
110
|
+
"constant",
|
|
111
|
+
"to_htt",
|
|
112
|
+
"clone",
|
|
113
|
+
"copy",
|
|
114
|
+
"detach",
|
|
115
|
+
"requires_grad_",
|
|
116
|
+
"to",
|
|
117
|
+
]
|
|
File without changes
|
hiepy/algebra/add.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""HTTensor addition module.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to compute the formal sum of two HT tensors
|
|
4
|
+
by embedding their transfer tensors and leaf matrices into block-diagonal structures.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import torch
|
|
10
|
+
|
|
11
|
+
from hiepy.httensor.httensor import HTTensor
|
|
12
|
+
from hiepy.utils.data_types import TensorDict
|
|
13
|
+
from hiepy.utils.dtype_device import check_device, result_dtype
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def add(x: HTTensor, y: HTTensor) -> HTTensor:
|
|
17
|
+
"""
|
|
18
|
+
Adds two HT tensors via block-diagonal embedding.
|
|
19
|
+
|
|
20
|
+
Constructs an HT tensor representing the exact sum of two HT tensors by embedding their
|
|
21
|
+
leaf matrices and transfer tensors into a block-diagonal HT representation.
|
|
22
|
+
|
|
23
|
+
The resulting tensor $Z = x + y$ is not computed in full tensor space, but
|
|
24
|
+
represented exactly in HT format as:
|
|
25
|
+
|
|
26
|
+
- Leaf matrices:
|
|
27
|
+
$U_i^Z = [U_i^x, U_i^y]$
|
|
28
|
+
|
|
29
|
+
- Transfer tensors:
|
|
30
|
+
$B_t^Z = diag(B_t^x, B_t^y)$
|
|
31
|
+
|
|
32
|
+
where the block-diagonal structure is applied at each node of the dimension tree.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
x : HTTensor
|
|
37
|
+
First HT tensor. Must share the same dimension tree and
|
|
38
|
+
mode sizes as ``y``.
|
|
39
|
+
|
|
40
|
+
y : HTTensor
|
|
41
|
+
Second HT tensor. Must share the same dimension tree and
|
|
42
|
+
mode sizes as ``x``.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
HTTensor
|
|
47
|
+
A new HT tensor representing the sum x + y in block-diagonal form.
|
|
48
|
+
|
|
49
|
+
Raises
|
|
50
|
+
------
|
|
51
|
+
ValueError
|
|
52
|
+
If ``x`` and ``y`` do not share the same dimension tree or shape.
|
|
53
|
+
|
|
54
|
+
RuntimeError
|
|
55
|
+
If ``x`` and ``y`` live on different devices.
|
|
56
|
+
|
|
57
|
+
Notes
|
|
58
|
+
-----
|
|
59
|
+
- The result has the promoted dtype of ``x`` and ``y`` (e.g. ``float32`` and ``float64`` give ``float64``).
|
|
60
|
+
- The dimension tree is preserved.
|
|
61
|
+
- The tensor shape is preserved.
|
|
62
|
+
- Rank growth is additive:
|
|
63
|
+
$r_t^{out} = r_t^x + r_t^y$
|
|
64
|
+
- Orthogonality is not preserved.
|
|
65
|
+
- The result typically requires recompression.
|
|
66
|
+
- The arithmetic cost of the operation is negligible, as it primarily involves the
|
|
67
|
+
concatenation of leaf matrices and the creation of block-diagonal transfer tensors.
|
|
68
|
+
|
|
69
|
+
Examples
|
|
70
|
+
--------
|
|
71
|
+
>>> import hiepy
|
|
72
|
+
>>> import torch
|
|
73
|
+
>>> torch.set_default_dtype(torch.float64)
|
|
74
|
+
>>> x = hiepy.randn(shape=(8, 8, 8))
|
|
75
|
+
>>> y = hiepy.randn(shape=(8, 8, 8))
|
|
76
|
+
>>> sum_hiepy = hiepy.add(x,y)
|
|
77
|
+
>>> x_t = x.full()
|
|
78
|
+
>>> y_t = y.full()
|
|
79
|
+
>>> sum_torch = x_t + y_t
|
|
80
|
+
>>> torch.allclose(sum_torch, sum_hiepy.full())
|
|
81
|
+
True
|
|
82
|
+
"""
|
|
83
|
+
# Validity checks
|
|
84
|
+
if not x.dtree == y.dtree:
|
|
85
|
+
raise ValueError("Dimension trees of 'x' and 'y' are not compatible")
|
|
86
|
+
if x.shape != y.shape:
|
|
87
|
+
raise ValueError(f"Shape of 'y' {y.shape} does not match shape of 'x' {x.shape}")
|
|
88
|
+
device = check_device(x=x, y=y)
|
|
89
|
+
dtype = result_dtype(x, y)
|
|
90
|
+
x, y = x.to(dtype=dtype), y.to(dtype=dtype)
|
|
91
|
+
|
|
92
|
+
U: TensorDict = {}
|
|
93
|
+
B: TensorDict = {}
|
|
94
|
+
dtree = x.dtree.copy()
|
|
95
|
+
|
|
96
|
+
# Concatenate leaf matrices
|
|
97
|
+
for leaf in dtree.get_leaves():
|
|
98
|
+
U[leaf] = torch.cat((x.U[leaf], y.U[leaf]), dim=1)
|
|
99
|
+
|
|
100
|
+
# Embed the transfer tensors in a 3D blockdiagonal way
|
|
101
|
+
for t in dtree.get_inner_nodes():
|
|
102
|
+
if x.dtree.is_root(t):
|
|
103
|
+
xl, xr, _ = x.B[t].shape # rank of node x children
|
|
104
|
+
yl, yr, _ = y.B[t].shape # rank of node y children
|
|
105
|
+
Bt = torch.zeros(xl + yl, xr + yr, 1, dtype=dtype, device=device)
|
|
106
|
+
Bt[:xl, :xr] = x.B[t]
|
|
107
|
+
Bt[xl:, xr:] = y.B[t]
|
|
108
|
+
else:
|
|
109
|
+
xl, xr, xk = x.B[t].shape # rank of node children and node x (left, right, node)
|
|
110
|
+
yl, yr, yk = y.B[t].shape # rank of node children and node y (left, right, node)
|
|
111
|
+
Bt = torch.zeros(xl + yl, xr + yr, xk + yk, dtype=dtype, device=device)
|
|
112
|
+
Bt[:xl, :xr, :xk] = x.B[t]
|
|
113
|
+
Bt[xl:, xr:, xk:] = y.B[t]
|
|
114
|
+
B[t] = Bt
|
|
115
|
+
|
|
116
|
+
# Create resulting HTTensor
|
|
117
|
+
res = HTTensor(U=U, B=B, dtree=dtree, is_orthog=False)
|
|
118
|
+
return res
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# trunc_sum is currently not maintained
|
|
122
|
+
|
|
123
|
+
# def trunc_sum(summands: list[HTTensor], opts: HTTensorOpts) -> HTTensor:
|
|
124
|
+
# """Add HTTensors contained in summands and truncate sum on the fly."""
|
|
125
|
+
# # input validity checks
|
|
126
|
+
# if not isinstance(summands, list):
|
|
127
|
+
# raise TypeError(f"Argument 'summands' must be of type list, but got {type(summands)}.")
|
|
128
|
+
|
|
129
|
+
# if not all(isinstance(item, HTTensor) for item in summands):
|
|
130
|
+
# raise TypeError("Argument 'summands' must only contain HTTensor objects.")
|
|
131
|
+
|
|
132
|
+
# if len(summands) < 2:
|
|
133
|
+
# raise ValueError(f"Argument 'summands' must contain at least two elements, but got {len(summands)}.")
|
|
134
|
+
|
|
135
|
+
# if not all(summands[0].dtree.is_equal(item.dtree) for item in summands):
|
|
136
|
+
# raise ValueError(
|
|
137
|
+
# "All HTTensors in 'summands' must share the same dimension tree, "
|
|
138
|
+
# "but at least one tensor has a non-matching tree."
|
|
139
|
+
# )
|
|
140
|
+
# check_opts(opts)
|
|
141
|
+
|
|
142
|
+
# # Copy first summand if required
|
|
143
|
+
# U, B, dtree, opts = {}, {}, deepcopy(summands[0].dtree), deepcopy(opts)
|
|
144
|
+
|
|
145
|
+
# # Anpassen der Fehlertoleranzen in opts
|
|
146
|
+
# # Soll global der Fehler e eingehalten werden, muss der Kuerzungsfehler pro Knoten
|
|
147
|
+
# # kleiner gleich e / sqrt((Tensorordnung * 2 - 2)) bleiben
|
|
148
|
+
# adjusted_opts = deepcopy(opts)
|
|
149
|
+
# if "atol" in adjusted_opts:
|
|
150
|
+
# adjusted_opts["atol"] = adjusted_opts["atol"] / (x.order * 2 - 3) ** 0.5
|
|
151
|
+
# if "rtol" in adjusted_opts:
|
|
152
|
+
# adjusted_opts["rtol"] = adjusted_opts["rtol"] / (x.order * 2 - 3) ** 0.5
|
|
153
|
+
|
|
154
|
+
# # Berechne die reduzierten Gram'schen Matrizen der impliziten Summe
|
|
155
|
+
# G = gramians_sum(summands)
|
|
156
|
+
|
|
157
|
+
# # Berechne gekuerzte Blattmatrizen der impliziten Summe
|
|
158
|
+
# # Update dabei on the fly den Transfertensor des Elternknotens
|
|
159
|
+
# B_upd = {node.dims: {} for node in dtree.get_inner_nodes()}
|
|
160
|
+
# for leaf in dtree.get_leaves():
|
|
161
|
+
# dim = leaf.dims
|
|
162
|
+
# # Konkatenieren der Blattmatrizen
|
|
163
|
+
# U_cat = torch.hstack(tuple(item.U[dim] for item in summands))
|
|
164
|
+
# # QR Zerlegung
|
|
165
|
+
# Q, R = torch.linalg.qr(U_cat, mode="reduced")
|
|
166
|
+
# # Update reduzierte Gram'sche Matrix
|
|
167
|
+
# G_upd = R @ G[dim] @ R.T
|
|
168
|
+
# # Berechne davon linke Singulaervektoren
|
|
169
|
+
# S, sv = left_svd_gramian(G_upd)
|
|
170
|
+
# # Berechne basierend auf opts wie viele Spalten behalten werden
|
|
171
|
+
# if adjusted_opts:
|
|
172
|
+
# rank = trunc_rank(sv, **adjusted_opts)
|
|
173
|
+
# else:
|
|
174
|
+
# rank = len(sv)
|
|
175
|
+
# S = S[:, :rank]
|
|
176
|
+
# # Berechne schliesslich finale Blattmatrix
|
|
177
|
+
# U[dim] = Q @ S
|
|
178
|
+
|
|
179
|
+
# # Update Transfertensor des Elternknotens
|
|
180
|
+
# # Adaptiere R entsprechend der Kuerzung
|
|
181
|
+
# R = S.T @ R
|
|
182
|
+
# # Teile dafuer zunaechst R in R=[R1 | R2 | ... | Rn]
|
|
183
|
+
# # wobei die Spaltenanzahl von Ri durch den entsprechenden Rang des i-ten Summanden
|
|
184
|
+
# # vorgegeben ist
|
|
185
|
+
# R = torch.split(R, [item.rank[dim] for item in summands], dim=1)
|
|
186
|
+
# # Multipliziere nun R[i] in den Transfertensor des Elternknotens des i-ten Summanden
|
|
187
|
+
# assert leaf.father is not None
|
|
188
|
+
# fdims = leaf.father.dims
|
|
189
|
+
# for i in range(len(summands)):
|
|
190
|
+
# if leaf.is_left():
|
|
191
|
+
# if i in B_upd[fdims]:
|
|
192
|
+
# B_upd[fdims][i] = torch.tensordot(R[i], B_upd[fdims][i], dims=([1], [0])) # type: ignore
|
|
193
|
+
# else:
|
|
194
|
+
# B_upd[fdims][i] = torch.tensordot(R[i], summands[i].B[fdims], dims=([1], [0])) # type: ignore
|
|
195
|
+
# else:
|
|
196
|
+
# if i in B_upd[fdims]:
|
|
197
|
+
# B_upd[fdims][i] = torch.tensordot(R[i], B_upd[fdims][i], dims=([1], [1])) # type: ignore
|
|
198
|
+
# B_upd[fdims][i] = torch.movedim(B_upd[fdims][i], source=0, destination=1)
|
|
199
|
+
# else:
|
|
200
|
+
# B_upd[fdims][i] = torch.tensordot(R[i], summands[i].B[fdims], dims=([1], [1])) # type: ignore
|
|
201
|
+
# B_upd[fdims][i] = torch.movedim(B_upd[fdims][i], source=0, destination=1)
|
|
202
|
+
|
|
203
|
+
# # Berechne nun gekuerzte Transfertensoren der impliziten Summe
|
|
204
|
+
# # Durschreite den Dimensionsbaum dazu bottom-up
|
|
205
|
+
# for level in dtree.levels(order="bottom-up"):
|
|
206
|
+
# for node in level:
|
|
207
|
+
# if node.is_leaf() or node.is_root():
|
|
208
|
+
# continue
|
|
209
|
+
# dims = node.dims
|
|
210
|
+
# # Konkateniere die aktualisierten Transfertensoren der Summanden
|
|
211
|
+
# B_cat = torch.cat([B_upd[dims][i] for i in range(len(summands))], dim=2)
|
|
212
|
+
# # Berechne QR Zerlegung der Matrizierung davon
|
|
213
|
+
# Q, R = torch.linalg.qr(matricise(B_cat, t=(0, 1)), mode="reduced")
|
|
214
|
+
# # Dematriziere Q zu 3D Transfertensor
|
|
215
|
+
# Q = dematricise(Q, shape=(B_cat.shape[0], B_cat.shape[1], Q.shape[1]), t=(0, 1))
|
|
216
|
+
|
|
217
|
+
# # Aktualisiere reduzierte Gram'sche Matrix
|
|
218
|
+
# G_upd = R @ G[dims] @ R.T
|
|
219
|
+
# # Berechne davon linke Singulaervektoren
|
|
220
|
+
# S, sv = left_svd_gramian(G_upd)
|
|
221
|
+
# # Berechne basierend auf opts wie viele Spalten behalten werden
|
|
222
|
+
# if adjusted_opts:
|
|
223
|
+
# rank = trunc_rank(sv, **adjusted_opts)
|
|
224
|
+
# else:
|
|
225
|
+
# rank = len(sv)
|
|
226
|
+
# S = S[:, :rank]
|
|
227
|
+
# # Berechne schliesslich finalen Transfertensor
|
|
228
|
+
# B[dims] = torch.tensordot(Q, S, dims=([2], [0])) # type: ignore
|
|
229
|
+
|
|
230
|
+
# # Update Transfertensor des Elternknotens
|
|
231
|
+
# # Adaptiere R entsprechend der Kuerzung
|
|
232
|
+
# R = S.T @ R
|
|
233
|
+
# # Teile dafuer zunaechst R in R=[R1 | R2 | ... | Rn]
|
|
234
|
+
# # wobei die Spaltenanzahl von Ri durch den entsprechenden Rang des i-ten Summanden
|
|
235
|
+
# # vorgegeben ist
|
|
236
|
+
# R = torch.split(R, [item.rank[dims] for item in summands], dim=1)
|
|
237
|
+
# # Multipliziere nun R[i] in den Transfertensor des Elternknotens des i-ten Summanden
|
|
238
|
+
# assert node.father is not None
|
|
239
|
+
# fdims = node.father.dims
|
|
240
|
+
# for i in range(len(summands)):
|
|
241
|
+
# if node.is_left():
|
|
242
|
+
# if i in B_upd[fdims]:
|
|
243
|
+
# B_upd[fdims][i] = torch.tensordot(R[i], B_upd[fdims][i], dims=([1], [0])) # type: ignore
|
|
244
|
+
# else:
|
|
245
|
+
# B_upd[fdims][i] = torch.tensordot(
|
|
246
|
+
# R[i],
|
|
247
|
+
# summands[i].B[fdims],
|
|
248
|
+
# dims=([1], [0]), # type: ignore
|
|
249
|
+
# )
|
|
250
|
+
# else:
|
|
251
|
+
# if i in B_upd[fdims]:
|
|
252
|
+
# B_upd[fdims][i] = torch.tensordot(R[i], B_upd[fdims][i], dims=([1], [1])) # type: ignore
|
|
253
|
+
# B_upd[fdims][i] = torch.movedim(B_upd[fdims][i], source=0, destination=1)
|
|
254
|
+
# else:
|
|
255
|
+
# B_upd[fdims][i] = torch.tensordot(
|
|
256
|
+
# R[i],
|
|
257
|
+
# summands[i].B[fdims],
|
|
258
|
+
# dims=([1], [1]), # type: ignore
|
|
259
|
+
# )
|
|
260
|
+
# B_upd[fdims][i] = torch.movedim(B_upd[fdims][i], source=0, destination=1)
|
|
261
|
+
# # Gebe B_upd[node] frei
|
|
262
|
+
# del B_upd[dims]
|
|
263
|
+
|
|
264
|
+
# # Wurzelbehandlung..
|
|
265
|
+
# B_root = torch.sum(torch.concat([item for item in B_upd[dtree.root.dims].values()], dim=2), dim=2)
|
|
266
|
+
# B[dtree.root.dims] = B_root.reshape(B_root.shape[0], B_root.shape[1], 1)
|
|
267
|
+
|
|
268
|
+
# # Erstelle HTucker Tensor der Summe
|
|
269
|
+
# z = HTTensor(U=U, B=B, dtree=dtree, is_orthog=False)
|
|
270
|
+
|
|
271
|
+
# return z
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""HTTensor contraction module.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to compute the full tensor contraction
|
|
4
|
+
(inner product) of two HT tensors sharing the same dimension tree.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import torch
|
|
10
|
+
|
|
11
|
+
from hiepy.httensor.httensor import HTTensor
|
|
12
|
+
from hiepy.utils.dtype_device import check_device, result_dtype
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def contract(x: HTTensor, y: HTTensor) -> torch.Tensor:
|
|
16
|
+
r"""
|
|
17
|
+
Computes the full inner product of two HT tensors.
|
|
18
|
+
|
|
19
|
+
This operation evaluates the scalar contraction
|
|
20
|
+
$\langle X, Y \rangle = \sum_{i_1,\dots,i_d} X_{i_1\dots i_d} Y_{i_1\dots i_d}$
|
|
21
|
+
without unfolding the full tensors, by performing a recursive contraction along the dimension tree.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
x : HTTensor
|
|
26
|
+
First HT tensor. Must share the same dimension tree and mode sizes as ``y``.
|
|
27
|
+
|
|
28
|
+
y : HTTensor
|
|
29
|
+
Second HT tensor. Must share the same dimension tree and mode sizes as ``x``.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
torch.Tensor
|
|
34
|
+
Scalar tensor representing the full inner product of ``x``and ``y``.
|
|
35
|
+
|
|
36
|
+
Raises
|
|
37
|
+
------
|
|
38
|
+
ValueError
|
|
39
|
+
If the dimension trees of ``x`` and ``y`` differ.
|
|
40
|
+
|
|
41
|
+
ValueError
|
|
42
|
+
If the tensor shapes of ``x`` and ``y`` do not match.
|
|
43
|
+
|
|
44
|
+
RuntimeError
|
|
45
|
+
If ``x`` and ``y`` live on different devices.
|
|
46
|
+
|
|
47
|
+
Notes
|
|
48
|
+
-----
|
|
49
|
+
- The result has the promoted dtype of ``x`` and ``y`` (e.g. ``float32`` and ``float64`` give ``float64``).
|
|
50
|
+
- The operation's stability is improved when ``x`` and ``y`` are orthogonal.
|
|
51
|
+
- No recompression is required, as the result is scalar-valued.
|
|
52
|
+
- The arithmetic cost of the operation scales as $\mathcal{O}(dnr_xr_y + dr_x^2r_y^2)$,
|
|
53
|
+
where $d$ is the tensor order, $n$ the maximum mode size, and $r_x$, $r_y$ the maximum
|
|
54
|
+
HT ranks of ``x`` and ``y``, respectively.
|
|
55
|
+
|
|
56
|
+
Examples
|
|
57
|
+
--------
|
|
58
|
+
>>> import torch
|
|
59
|
+
>>> import hiepy
|
|
60
|
+
>>> torch.set_default_dtype(torch.float64)
|
|
61
|
+
>>> shape = 8,8,8,8
|
|
62
|
+
>>> x = torch.randn(shape)
|
|
63
|
+
>>> y = torch.randn(shape)
|
|
64
|
+
>>> prod_torch = torch.sum(x*y)
|
|
65
|
+
>>> opts = hiepy.TruncOpts({"atol":1e-10})
|
|
66
|
+
>>> x_ht = hiepy.to_htt(x,opts)
|
|
67
|
+
>>> y_ht = hiepy.to_htt(y,opts)
|
|
68
|
+
>>> prod_ht = hiepy.contract(x_ht,y_ht)
|
|
69
|
+
>>> torch.allclose(prod_torch, prod_ht)
|
|
70
|
+
True
|
|
71
|
+
"""
|
|
72
|
+
# Validity checks
|
|
73
|
+
if not x.dtree == y.dtree:
|
|
74
|
+
raise ValueError("Dimension trees of 'x' and 'y' are not compatible")
|
|
75
|
+
if x.shape != y.shape:
|
|
76
|
+
raise ValueError(f"Shape of 'y' {y.shape} does not match shape of 'x' {x.shape}")
|
|
77
|
+
check_device(x=x, y=y)
|
|
78
|
+
dtype = result_dtype(x, y)
|
|
79
|
+
x, y = x.to(dtype=dtype), y.to(dtype=dtype)
|
|
80
|
+
|
|
81
|
+
dtree = x.dtree
|
|
82
|
+
Ux, Uy = x.U, y.U
|
|
83
|
+
Bx, By = x.B, y.B
|
|
84
|
+
M = {}
|
|
85
|
+
|
|
86
|
+
# Contract leaf matrices
|
|
87
|
+
for leaf in dtree.get_leaves():
|
|
88
|
+
M[leaf] = Ux[leaf].T @ Uy[leaf]
|
|
89
|
+
|
|
90
|
+
# Contract M and transfer tensors bottom-up along the tree
|
|
91
|
+
for level in dtree.get_levels(order="bottom-up"):
|
|
92
|
+
for t in level:
|
|
93
|
+
if dtree.is_leaf(t):
|
|
94
|
+
continue
|
|
95
|
+
left, right = dtree.get_children(t)
|
|
96
|
+
M_ = torch.tensordot(M[left], By[t], dims=([1], [0])) # type:ignore
|
|
97
|
+
M_ = torch.tensordot(Bx[t], M_, dims=([0], [0])) # type:ignore
|
|
98
|
+
M_ = torch.tensordot(M_, M[right], dims=([0, 2], [0, 1])) # type:ignore
|
|
99
|
+
M[t] = M_
|
|
100
|
+
|
|
101
|
+
return M[dtree.root][0, 0]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Implements the elementwise mode-product of a vector with an HT tensor.
|
|
3
|
+
|
|
4
|
+
This module provides functionality to compute the elementwise mode-product of a vector
|
|
5
|
+
and an HT tensor.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import torch
|
|
11
|
+
|
|
12
|
+
from hiepy.httensor.httensor import HTTensor as HTTensor
|
|
13
|
+
from hiepy.utils.dtype_device import check_device, result_dtype
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def ele_mode_mul(x: HTTensor, v: torch.Tensor, dim: int) -> HTTensor:
|
|
17
|
+
r"""
|
|
18
|
+
Scales an HT tensor ``x`` along its mode ``dim`` by a vector ``v``.
|
|
19
|
+
|
|
20
|
+
The elementwise mode multiplication of a tensor $X\in\mathbb{R}^{n_0\times\dots\times n_{d-1}}$ with a vector
|
|
21
|
+
$v \in \mathbb{R}^{n_{\mu}}$ along mode $\mu \in \{0,\dots,d-1\}$ is defined as:
|
|
22
|
+
|
|
23
|
+
$$(X \star_{\mu} v)_{i_0,\dots,i_{d-1}} = X_{i_0,\dots,i_{d-1}} \cdot v_{i_\mu}$$
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
x : HTTensor
|
|
29
|
+
Input HT tensor.
|
|
30
|
+
|
|
31
|
+
v : torch.Tensor
|
|
32
|
+
One-dimensional vector defining the mode-wise scaling factors.
|
|
33
|
+
Its length must coincide with the size of mode ``dim``.
|
|
34
|
+
|
|
35
|
+
dim : int
|
|
36
|
+
Mode along which ``x`` is scaled by ``v``.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
HTTensor
|
|
41
|
+
A new HT tensor representing the scaled tensor.
|
|
42
|
+
|
|
43
|
+
Raises
|
|
44
|
+
------
|
|
45
|
+
ValueError
|
|
46
|
+
If ```v``` is not one-dimensional.
|
|
47
|
+
|
|
48
|
+
ValueError
|
|
49
|
+
If ```dim``` is outside the valid mode range.
|
|
50
|
+
|
|
51
|
+
ValueError
|
|
52
|
+
If the length of ```v``` does not match the size of the selected mode.
|
|
53
|
+
|
|
54
|
+
RuntimeError
|
|
55
|
+
If ``x`` and ``v`` live on different devices.
|
|
56
|
+
|
|
57
|
+
Notes
|
|
58
|
+
-----
|
|
59
|
+
- The result has the promoted dtype of ``x`` and ``v`` (e.g. ``float32`` and ``float64`` give ``float64``).
|
|
60
|
+
- The dimension tree is preserved.
|
|
61
|
+
- Hierarchical ranks are preserved.
|
|
62
|
+
- Orthogonality is not preserved.
|
|
63
|
+
- Computational cost is linear in mode size and corresponding rank, $\mathcal{O}(nr)$.
|
|
64
|
+
- All transfer tensors remain unchanged.
|
|
65
|
+
- All but one leaf matrices remain unchanged.
|
|
66
|
+
|
|
67
|
+
Examples
|
|
68
|
+
--------
|
|
69
|
+
>>> import torch
|
|
70
|
+
>>> import hiepy
|
|
71
|
+
>>> torch.set_default_dtype(torch.float64)
|
|
72
|
+
>>> x_ht = hiepy.arange((2, 3, 4))
|
|
73
|
+
>>> x_ht.full()
|
|
74
|
+
tensor([[[ 0., 1., 2., 3.],
|
|
75
|
+
[ 4., 5., 6., 7.],
|
|
76
|
+
[ 8., 9., 10., 11.]],
|
|
77
|
+
<BLANKLINE>
|
|
78
|
+
[[12., 13., 14., 15.],
|
|
79
|
+
[16., 17., 18., 19.],
|
|
80
|
+
[20., 21., 22., 23.]]])
|
|
81
|
+
>>> v = torch.arange(3)
|
|
82
|
+
>>> hiepy.ele_mode_mul(x_ht,v,dim=1).full()
|
|
83
|
+
tensor([[[ 0., 0., 0., 0.],
|
|
84
|
+
[ 4., 5., 6., 7.],
|
|
85
|
+
[16., 18., 20., 22.]],
|
|
86
|
+
<BLANKLINE>
|
|
87
|
+
[[ 0., 0., 0., 0.],
|
|
88
|
+
[16., 17., 18., 19.],
|
|
89
|
+
[40., 42., 44., 46.]]])
|
|
90
|
+
"""
|
|
91
|
+
# Validity checks
|
|
92
|
+
if v.ndim != 1:
|
|
93
|
+
raise ValueError(f"Argument 'v' must be a 1D torch.Tensor, got shape {v.shape}")
|
|
94
|
+
if dim < 0 or dim >= x.order:
|
|
95
|
+
raise ValueError(f"Argument 'dim' is out of range for this HTTensor (dim={dim}, order={x.order})")
|
|
96
|
+
if x.shape[dim] != v.shape[0]:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
f"Mismatch between dimension {dim} of x (size {x.shape[dim]}) and length of v (size {v.shape[0]})."
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
check_device(x=x, v=v)
|
|
102
|
+
dtype = result_dtype(x, v)
|
|
103
|
+
x = x.clone().to(dtype=dtype)
|
|
104
|
+
v = v.to(dtype=dtype)
|
|
105
|
+
|
|
106
|
+
# Update leaf matrix corresponding to dim
|
|
107
|
+
node = (dim,)
|
|
108
|
+
x.U[node] = x.U[node] * (v[:, None])
|
|
109
|
+
|
|
110
|
+
# Update is_orthog
|
|
111
|
+
x.is_orthog = False
|
|
112
|
+
|
|
113
|
+
return x
|