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.
Files changed (49) hide show
  1. hiepy/__init__.py +117 -0
  2. hiepy/algebra/__init__.py +0 -0
  3. hiepy/algebra/add.py +271 -0
  4. hiepy/algebra/contract.py +101 -0
  5. hiepy/algebra/ele_mode_mul.py +113 -0
  6. hiepy/algebra/ele_mul.py +324 -0
  7. hiepy/algebra/mode_mul.py +104 -0
  8. hiepy/algebra/scalar_mul.py +85 -0
  9. hiepy/algebra/sub.py +118 -0
  10. hiepy/factory/__init__.py +0 -0
  11. hiepy/factory/arange.py +105 -0
  12. hiepy/factory/constant.py +107 -0
  13. hiepy/factory/copy.py +231 -0
  14. hiepy/factory/hilbert.py +109 -0
  15. hiepy/factory/ones.py +102 -0
  16. hiepy/factory/randn.py +305 -0
  17. hiepy/factory/to_htt.py +158 -0
  18. hiepy/factory/zeros.py +84 -0
  19. hiepy/httensor/__init__.py +0 -0
  20. hiepy/httensor/full.py +75 -0
  21. hiepy/httensor/get_item.py +190 -0
  22. hiepy/httensor/getter.py +262 -0
  23. hiepy/httensor/gramians.py +212 -0
  24. hiepy/httensor/httensor.py +283 -0
  25. hiepy/httensor/io.py +83 -0
  26. hiepy/linalg/__init__.py +0 -0
  27. hiepy/linalg/diagonal_tensor.py +68 -0
  28. hiepy/linalg/left_svd_gramian.py +86 -0
  29. hiepy/linalg/local_left_svd.py +128 -0
  30. hiepy/linalg/matricize.py +160 -0
  31. hiepy/linalg/mode_mul_torch.py +298 -0
  32. hiepy/linalg/norm.py +46 -0
  33. hiepy/linalg/vectorize.py +98 -0
  34. hiepy/py.typed +0 -0
  35. hiepy/transformation/__init__.py +0 -0
  36. hiepy/transformation/orthog.py +106 -0
  37. hiepy/transformation/squeeze.py +162 -0
  38. hiepy/transformation/truncate.py +362 -0
  39. hiepy/utils/__init__.py +0 -0
  40. hiepy/utils/checks.py +58 -0
  41. hiepy/utils/data_types.py +59 -0
  42. hiepy/utils/dimtree.py +334 -0
  43. hiepy/utils/dtype_device.py +87 -0
  44. hiepy/utils/trunc_rank.py +89 -0
  45. hiepy-0.1.0.dist-info/METADATA +91 -0
  46. hiepy-0.1.0.dist-info/RECORD +49 -0
  47. hiepy-0.1.0.dist-info/WHEEL +5 -0
  48. hiepy-0.1.0.dist-info/licenses/LICENSE +156 -0
  49. 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