diff --git a/docs/_build/doctest/output.txt b/docs/_build/doctest/output.txt index 3a24cc5..f46190b 100644 --- a/docs/_build/doctest/output.txt +++ b/docs/_build/doctest/output.txt @@ -1,28 +1,20 @@ -Results of doctest builder run on 2024-04-14 01:59:36 +Results of doctest builder run on 2024-04-18 22:09:14 ===================================================== Document: embeddings -------------------- 1 items passed all tests: - 35 tests in default -35 tests in 1 items. -35 passed and 0 failed. + 45 tests in default +45 tests in 1 items. +45 passed and 0 failed. Test passed. Document: models ---------------- 1 items passed all tests: - 115 tests in default -115 tests in 1 items. -115 passed and 0 failed. -Test passed. - -Document: operations --------------------- -1 items passed all tests: - 186 tests in default -186 tests in 1 items. -186 passed and 0 failed. + 118 tests in default +118 tests in 1 items. +118 passed and 0 failed. Test passed. Document: components @@ -35,8 +27,8 @@ Expected: tensor([[-0.2799, -0.4383, -0.8387], [ 1.6225, -0.3370, -1.2316]]) Got: - tensor([[ 0.2628, -0.6672, -0.9051], - [-0.6796, -0.4245, -0.5780]]) + tensor([[-0.4871, -0.3151, -0.9507], + [ 0.2821, -2.0437, -0.1693]]) ********************************************************************** File "../tensorkrowch/components.py", line ?, in default Failed example: @@ -44,7 +36,7 @@ Failed example: Expected: tensor(-1.5029) Got: - tensor(-2.9916) + tensor(-3.6837) ********************************************************************** File "../tensorkrowch/components.py", line ?, in default Failed example: @@ -52,7 +44,7 @@ Failed example: Expected: tensor([ 1.3427, -0.7752, -2.0704]) Got: - tensor([-0.4168, -1.0916, -1.4831]) + tensor([-0.2050, -2.3588, -1.1199]) ********************************************************************** File "../tensorkrowch/components.py", line ?, in default Failed example: @@ -61,8 +53,8 @@ Expected: tensor([[ 1.4005, -0.0521, -1.2091], [ 1.9844, 0.3513, -0.5920]]) Got: - tensor([[-0.5314, -0.7805, -0.6475], - [-0.1279, 0.7409, 0.5816]]) + tensor([[ 0.9150, -0.8719, -0.6474], + [ 0.6041, 1.1371, 1.9903]]) ********************************************************************** File "../tensorkrowch/components.py", line ?, in default Failed example: @@ -70,7 +62,7 @@ Failed example: Expected: tensor(0.3139) Got: - tensor(-0.1275) + tensor(0.5212) ********************************************************************** File "../tensorkrowch/components.py", line ?, in default Failed example: @@ -78,7 +70,7 @@ Failed example: Expected: tensor([ 1.6925, 0.1496, -0.9006]) Got: - tensor([-0.3296, -0.0198, -0.0330]) + tensor([0.7595, 0.1326, 0.6714]) ********************************************************************** File "../tensorkrowch/components.py", line ?, in default Failed example: @@ -87,8 +79,8 @@ Expected: tensor([[ 0.2111, -0.9551, -0.7812], [ 0.2254, 0.3381, -0.2461]]) Got: - tensor([[-1.8500, -0.4944, 0.5940], - [-0.9353, -0.6008, -0.4320]]) + tensor([[ 0.1216, 0.8937, 0.3488], + [-0.0591, 0.2322, 0.4997]]) ********************************************************************** File "../tensorkrowch/components.py", line ?, in default Failed example: @@ -96,7 +88,7 @@ Failed example: Expected: tensor(0.5567) Got: - tensor(0.7922) + tensor(0.3319) ********************************************************************** File "../tensorkrowch/components.py", line ?, in default Failed example: @@ -104,7 +96,7 @@ Failed example: Expected: tensor([0.0101, 0.9145, 0.3784]) Got: - tensor([0.6468, 0.0752, 0.7255]) + tensor([0.1278, 0.4678, 0.1068]) ********************************************************************** File "../tensorkrowch/components.py", line ?, in default Failed example: @@ -113,8 +105,8 @@ Expected: tensor([[ 1.5570, 1.8441, -0.0743], [ 0.4572, 0.7592, 0.6356]]) Got: - tensor([[ 1.5611, -0.1317, -1.2049], - [-0.8746, 0.7130, -1.7564]]) + tensor([[-0.3278, -0.6991, -0.2752], + [ 0.1391, 0.8034, 0.7103]]) ********************************************************************** File "../tensorkrowch/components.py", line ?, in default Failed example: @@ -122,7 +114,7 @@ Failed example: Expected: tensor(2.6495) Got: - tensor(2.8748) + tensor(1.3569) ********************************************************************** File "../tensorkrowch/components.py", line ?, in default Failed example: @@ -130,7 +122,7 @@ Failed example: Expected: tensor([1.6227, 1.9942, 0.6399]) Got: - tensor([1.7894, 0.7251, 2.1299]) + tensor([0.3561, 1.0650, 0.7617]) ********************************************************************** File "../tensorkrowch/components.py", line ?, in default Failed example: @@ -161,17 +153,17 @@ Got: Node( name: my_node tensor: - tensor([[[-0.2232, -0.0576], - [-2.0650, -1.6126], - [ 0.3369, -0.6012], - [-1.5875, -0.0953], - [-0.3129, 0.5625]], + tensor([[[ 0.5755, -0.2232], + [ 0.0960, 0.7366], + [ 0.0692, 0.5901], + [-1.0679, 0.3018], + [ 1.3321, -0.9018]], - [[ 2.0660, -0.9154], - [ 0.0753, 0.9608], - [ 0.7278, -0.2497], - [ 0.9090, -0.5242], - [ 1.4432, 2.6149]]]) + [[ 0.4797, 0.3139], + [ 0.1743, 0.2144], + [-0.0058, -1.5278], + [ 0.6321, -3.0880], + [ 2.0286, -0.9725]]]) axes: [left input @@ -210,17 +202,17 @@ Got: Node( name: node tensor: - tensor([[[-0.0399, 0.5602], - [-1.3402, 1.1127], - [ 0.0795, -1.6100], - [ 2.3359, 1.8294], - [-0.2258, -0.2929]], + tensor([[[-0.3102, 0.6564], + [-0.3489, 0.7021], + [ 0.3633, 0.7338], + [-1.4228, 0.2670], + [-1.0312, 0.8637]], - [[ 0.2125, -1.1086], - [-0.6906, -0.8028], - [-1.4547, 0.0981], - [-1.5869, 0.2020], - [-0.5355, -0.1357]]]) + [[-0.2284, -0.0338], + [-1.4974, -1.9423], + [ 0.6442, 0.5389], + [ 0.1022, 0.4674], + [ 0.0568, 0.8180]]]) axes: [axis_0 axis_1 @@ -272,17 +264,17 @@ Got: name: my_paramnode tensor: Parameter containing: - tensor([[[ 1.0837, 1.7036], - [-0.8799, -0.1600], - [-0.5867, -1.7778], - [ 0.1615, 1.0249], - [ 0.1880, 1.1234]], + tensor([[[-1.9965, -2.3411], + [ 1.8579, 0.0275], + [-1.4138, -0.8612], + [-1.9062, -0.0336], + [-0.5938, 0.4889]], - [[ 0.7499, -0.7739], - [-0.5278, 1.1947], - [-0.7586, -1.4460], - [-0.1016, -0.1954], - [ 0.1579, 0.0572]]], requires_grad=True) + [[-1.8927, 1.2207], + [-1.3008, 0.3132], + [-0.4851, -0.8716], + [ 0.1814, 0.2141], + [ 1.3394, 1.5221]]], requires_grad=True) axes: [left input @@ -323,17 +315,17 @@ Got: name: paramnode tensor: Parameter containing: - tensor([[[-0.7419, 0.3802], - [ 0.4183, 0.2084], - [-0.4384, -0.6852], - [-0.2616, -0.4692], - [ 0.4573, 2.0465]], + tensor([[[-2.0722, -2.1124], + [-0.7837, -0.8056], + [ 0.9438, -0.3114], + [-1.0441, -0.3426], + [ 0.9023, -0.4975]], - [[ 0.5333, 0.6722], - [-0.9125, 0.1184], - [ 0.1689, -0.0498], - [-1.2576, 0.5516], - [-0.0805, -0.2896]]], requires_grad=True) + [[-0.4780, 0.7928], + [ 0.8449, 0.2442], + [-2.0929, 0.7116], + [ 0.7338, -1.6473], + [-0.3100, -1.2454]]], requires_grad=True) axes: [axis_0 axis_1 @@ -352,8 +344,8 @@ Expected: [ 1.3371, 1.4761, 0.6551]], requires_grad=True) Got: Parameter containing: - tensor([[-1.4491, 0.7555, -1.2827], - [ 1.3534, -1.0071, 0.1320]], requires_grad=True) + tensor([[ 1.2550, -1.5785, -0.3669], + [ 0.9248, 0.9656, 0.0411]], requires_grad=True) ********************************************************************** File "../tensorkrowch/components.py", line ?, in default Failed example: @@ -404,14 +396,22 @@ Got: data_0[feature] <-> nodeA[input]]) ********************************************************************** 1 items had failures: - 21 of 395 in default -395 tests in 1 items. -374 passed and 21 failed. + 21 of 398 in default +398 tests in 1 items. +377 passed and 21 failed. ***Test Failed*** 21 failures. +Document: operations +-------------------- +1 items passed all tests: + 214 tests in default +214 tests in 1 items. +214 passed and 0 failed. +Test passed. + Doctest summary =============== - 731 tests + 775 tests 21 failures in tests 0 failures in setup code 0 failures in cleanup code diff --git a/docs/_build/doctrees/components.doctree b/docs/_build/doctrees/components.doctree index eecbe5a..ff01a66 100644 Binary files a/docs/_build/doctrees/components.doctree and b/docs/_build/doctrees/components.doctree differ diff --git a/docs/_build/doctrees/decompositions.doctree b/docs/_build/doctrees/decompositions.doctree index 949a341..07e0669 100644 Binary files a/docs/_build/doctrees/decompositions.doctree and b/docs/_build/doctrees/decompositions.doctree differ diff --git a/docs/_build/doctrees/embeddings.doctree b/docs/_build/doctrees/embeddings.doctree index 280fc43..ff6c065 100644 Binary files a/docs/_build/doctrees/embeddings.doctree and b/docs/_build/doctrees/embeddings.doctree differ diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle index 505278e..3d6f37b 100644 Binary files a/docs/_build/doctrees/environment.pickle and b/docs/_build/doctrees/environment.pickle differ diff --git a/docs/_build/doctrees/initializers.doctree b/docs/_build/doctrees/initializers.doctree index e068d57..3872782 100644 Binary files a/docs/_build/doctrees/initializers.doctree and b/docs/_build/doctrees/initializers.doctree differ diff --git a/docs/_build/doctrees/models.doctree b/docs/_build/doctrees/models.doctree index 507e800..5b99143 100644 Binary files a/docs/_build/doctrees/models.doctree and b/docs/_build/doctrees/models.doctree differ diff --git a/docs/_build/doctrees/operations.doctree b/docs/_build/doctrees/operations.doctree index 214d178..1bfce71 100644 Binary files a/docs/_build/doctrees/operations.doctree and b/docs/_build/doctrees/operations.doctree differ diff --git a/docs/_build/doctrees/tutorials/2_contracting_tensor_network.doctree b/docs/_build/doctrees/tutorials/2_contracting_tensor_network.doctree index db06df8..b85fbd5 100644 Binary files a/docs/_build/doctrees/tutorials/2_contracting_tensor_network.doctree and b/docs/_build/doctrees/tutorials/2_contracting_tensor_network.doctree differ diff --git a/docs/_build/doctrees/tutorials/5_subclass_tensor_network.doctree b/docs/_build/doctrees/tutorials/5_subclass_tensor_network.doctree index cd66ae0..ceb51f7 100644 Binary files a/docs/_build/doctrees/tutorials/5_subclass_tensor_network.doctree and b/docs/_build/doctrees/tutorials/5_subclass_tensor_network.doctree differ diff --git a/docs/_build/html/.buildinfo b/docs/_build/html/.buildinfo index d4bd8fd..2c2375e 100644 --- a/docs/_build/html/.buildinfo +++ b/docs/_build/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 63305a93c3c4aabf898dfa68c6787279 +config: c72caa67f9b4991a6c17c8f5c88f410a tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_build/html/_modules/index.html b/docs/_build/html/_modules/index.html index 16b667a..7b886e9 100644 --- a/docs/_build/html/_modules/index.html +++ b/docs/_build/html/_modules/index.html @@ -5,7 +5,7 @@ - Overview: module code — TensorKrowch 1.0.1 documentation + Overview: module code — TensorKrowch 1.1.0 documentation diff --git a/docs/_build/html/_modules/tensorkrowch/components.html b/docs/_build/html/_modules/tensorkrowch/components.html index ee163b0..0328352 100644 --- a/docs/_build/html/_modules/tensorkrowch/components.html +++ b/docs/_build/html/_modules/tensorkrowch/components.html @@ -5,7 +5,7 @@ - tensorkrowch.components — TensorKrowch 1.0.1 documentation + tensorkrowch.components — TensorKrowch 1.1.0 documentation @@ -1103,6 +1103,11 @@

Source code for tensorkrowch.components

                                                              List['AbstractNode']]:
         """
         Returns the neighbours of the node, the nodes to which it is connected.
+        
+        If ``self`` is a ``resultant`` node, this will return the neighbours of
+        the ``leaf`` nodes from which ``self`` inherits the edges. Therefore,
+        one cannot check if two ``resultant`` nodes are connected by looking
+        into their neighbours lists. To do that, use :meth:`is_connected_to`.
 
         Parameters
         ----------
@@ -2142,7 +2147,8 @@ 

Source code for tensorkrowch.components

 
 
[docs] def norm(self, p: Union[int, float] = 2, - axis: Optional[Union[Ax, Sequence[Ax]]] = None) -> Tensor: + axis: Optional[Union[Ax, Sequence[Ax]]] = None, + keepdim: bool = False) -> Tensor: """ Returns the norm of all elements in the node's tensor. If an ``axis`` is specified, the norm is over that axis. If ``axis`` is a sequence of axes, @@ -2159,6 +2165,9 @@

Source code for tensorkrowch.components

             The order of the norm.
         axis : int, str, Axis or list[int, str or Axis], optional
             Axis or sequence of axes over which to reduce.
+        keepdim : bool
+            Boolean indicating whether the output tensor have dimensions
+            retained or not.
 
         Returns
         -------
@@ -2184,7 +2193,7 @@ 

Source code for tensorkrowch.components

                     axis_num.append(self.get_axis_num(ax))
             else:
                 axis_num.append(self.get_axis_num(axis))
-        return self.tensor.norm(p=p, dim=axis_num)
+ return self.tensor.norm(p=p, dim=axis_num, keepdim=keepdim)
[docs] def numel(self) -> Tensor: """ diff --git a/docs/_build/html/_modules/tensorkrowch/decompositions/svd_decompositions.html b/docs/_build/html/_modules/tensorkrowch/decompositions/svd_decompositions.html index 272e13e..d9f1e59 100644 --- a/docs/_build/html/_modules/tensorkrowch/decompositions/svd_decompositions.html +++ b/docs/_build/html/_modules/tensorkrowch/decompositions/svd_decompositions.html @@ -5,7 +5,7 @@ - tensorkrowch.decompositions.svd_decompositions — TensorKrowch 1.0.1 documentation + tensorkrowch.decompositions.svd_decompositions — TensorKrowch 1.1.0 documentation @@ -303,7 +303,8 @@

Source code for tensorkrowch.decompositions.svd_decompositions

n_batches: int = 0, rank: Optional[int] = None, cum_percentage: Optional[float] = None, - cutoff: Optional[float] = None) -> List[torch.Tensor]: + cutoff: Optional[float] = None, + renormalize: bool = False) -> List[torch.Tensor]: r""" Splits a vector into a sequence of MPS tensors via consecutive SVD decompositions. The resultant tensors can be used to instantiate a @@ -356,6 +357,14 @@

Source code for tensorkrowch.decompositions.svd_decompositions

cum\_percentage cutoff : float, optional Quantity that lower bounds singular values in order to be kept. + renormalize : bool + Indicates whether nodes should be renormalized after SVD/QR + decompositions. If not, it may happen that the norm explodes as it + is being accumulated from all nodes. Renormalization aims to avoid + this undesired behavior by extracting the norm of each node on a + logarithmic scale after SVD/QR decompositions are computed. Finally, + the normalization factor is evenly distributed among all nodes of + the MPS. Returns ------- @@ -371,20 +380,21 @@

Source code for tensorkrowch.decompositions.svd_decompositions

batches_shape = vec.shape[:n_batches] phys_dims = torch.tensor(vec.shape[n_batches:]) + log_norm = 0 prev_bond = 1 tensors = [] for i in range(len(phys_dims) - 1): - vec = vec.view(*batches_shape, - prev_bond * phys_dims[i], - phys_dims[(i + 1):].prod()) + vec = vec.reshape(*batches_shape, + prev_bond * phys_dims[i], + phys_dims[(i + 1):].prod()) u, s, vh = torch.linalg.svd(vec, full_matrices=False) lst_ranks = [] if rank is None: - rank = s.shape[-1] - lst_ranks.append(rank) + aux_rank = s.shape[-1] + lst_ranks.append(aux_rank) else: lst_ranks.append(min(max(1, int(rank)), s.shape[-1])) @@ -395,7 +405,7 @@

Source code for tensorkrowch.decompositions.svd_decompositions

cp_rank = torch.lt( s_percentages, cum_percentage_tensor - ).view(-1, s.shape[-1]).all(dim=0).sum() + ).view(-1, s.shape[-1]).any(dim=0).sum() lst_ranks.append(max(1, cp_rank.item() + 1)) if cutoff is not None: @@ -403,32 +413,45 @@

Source code for tensorkrowch.decompositions.svd_decompositions

co_rank = torch.ge( s, cutoff_tensor - ).view(-1, s.shape[-1]).all(dim=0).sum() + ).view(-1, s.shape[-1]).any(dim=0).sum() lst_ranks.append(max(1, co_rank.item())) # Select rank from specified restrictions - rank = min(lst_ranks) + aux_rank = min(lst_ranks) - u = u[..., :rank] + u = u[..., :aux_rank] if i > 0: - u = u.reshape(*batches_shape, prev_bond, phys_dims[i], rank) + u = u.reshape(*batches_shape, prev_bond, phys_dims[i], aux_rank) - s = s[..., :rank] - vh = vh[..., :rank, :] - vh = torch.diag_embed(s) @ vh + s = s[..., :aux_rank] + vh = vh[..., :aux_rank, :] + + if renormalize: + aux_norm = s.norm(dim=-1, keepdim=True) + if not aux_norm.isinf().any() and (aux_norm > 0).any(): + s = s / aux_norm + log_norm += aux_norm.log() tensors.append(u) - prev_bond = rank + prev_bond = aux_rank vec = torch.diag_embed(s) @ vh tensors.append(vec) + + if log_norm is not 0: + rescale = (log_norm / len(tensors)).exp() + for vec in tensors: + vec *= rescale.view(*vec.shape[:n_batches], + *([1] * len(vec.shape[n_batches:]))) + return tensors
[docs]def mat_to_mpo(mat: torch.Tensor, rank: Optional[int] = None, cum_percentage: Optional[float] = None, - cutoff: Optional[float] = None) -> List[torch.Tensor]: + cutoff: Optional[float] = None, + renormalize: bool = False) -> List[torch.Tensor]: r""" Splits a matrix into a sequence of MPO tensors via consecutive SVD decompositions. The resultant tensors can be used to instantiate a @@ -468,6 +491,14 @@

Source code for tensorkrowch.decompositions.svd_decompositions

cum\_percentage cutoff : float, optional Quantity that lower bounds singular values in order to be kept. + renormalize : bool + Indicates whether nodes should be renormalized after SVD/QR + decompositions. If not, it may happen that the norm explodes as it + is being accumulated from all nodes. Renormalization aims to avoid + this undesired behavior by extracting the norm of each node on a + logarithmic scale after SVD/QR decompositions are computed. Finally, + the normalization factor is evenly distributed among all nodes of + the MPS. Returns ------- @@ -482,19 +513,20 @@

Source code for tensorkrowch.decompositions.svd_decompositions

if len(in_out_dims) == 2: return [mat] + log_norm = 0 prev_bond = 1 tensors = [] for i in range(0, len(in_out_dims) - 2, 2): - mat = mat.view(prev_bond * in_out_dims[i] * in_out_dims[i + 1], - in_out_dims[(i + 2):].prod()) + mat = mat.reshape(prev_bond * in_out_dims[i] * in_out_dims[i + 1], + in_out_dims[(i + 2):].prod()) u, s, vh = torch.linalg.svd(mat, full_matrices=False) lst_ranks = [] if rank is None: - rank = s.shape[-1] - lst_ranks.append(rank) + aux_rank = s.shape[-1] + lst_ranks.append(aux_rank) else: lst_ranks.append(min(max(1, int(rank)), s.shape[-1])) @@ -505,7 +537,7 @@

Source code for tensorkrowch.decompositions.svd_decompositions

cp_rank = torch.lt( s_percentages, cum_percentage_tensor - ).view(-1, s.shape[-1]).all(dim=0).sum() + ).view(-1, s.shape[-1]).any(dim=0).sum() lst_ranks.append(max(1, cp_rank.item() + 1)) if cutoff is not None: @@ -513,30 +545,41 @@

Source code for tensorkrowch.decompositions.svd_decompositions

co_rank = torch.ge( s, cutoff_tensor - ).view(-1, s.shape[-1]).all(dim=0).sum() + ).view(-1, s.shape[-1]).any(dim=0).sum() lst_ranks.append(max(1, co_rank.item())) # Select rank from specified restrictions - rank = min(lst_ranks) + aux_rank = min(lst_ranks) - u = u[..., :rank] + u = u[..., :aux_rank] if i == 0: - u = u.reshape(in_out_dims[i], in_out_dims[i + 1], rank) - u = u.permute(0, 2, 1) # left x input x right + u = u.reshape(in_out_dims[i], in_out_dims[i + 1], aux_rank) + u = u.permute(0, 2, 1) # input x right x output else: - u = u.reshape(prev_bond, in_out_dims[i], in_out_dims[i + 1], rank) + u = u.reshape(prev_bond, in_out_dims[i], in_out_dims[i + 1], aux_rank) u = u.permute(0, 1, 3, 2) # left x input x right x output - s = s[..., :rank] - vh = vh[..., :rank, :] - vh = torch.diag_embed(s) @ vh + s = s[..., :aux_rank] + vh = vh[..., :aux_rank, :] + + if renormalize: + aux_norm = s.norm(dim=-1) + if not aux_norm.isinf() and (aux_norm > 0): + s = s / aux_norm + log_norm += aux_norm.log() tensors.append(u) - prev_bond = rank + prev_bond = aux_rank mat = torch.diag_embed(s) @ vh - mat = mat.reshape(rank, in_out_dims[-2], in_out_dims[-1]) + mat = mat.reshape(aux_rank, in_out_dims[-2], in_out_dims[-1]) tensors.append(mat) + + if renormalize: + rescale = (log_norm / len(tensors)).exp() + for mat in tensors: + mat *= rescale + return tensors
diff --git a/docs/_build/html/_modules/tensorkrowch/embeddings.html b/docs/_build/html/_modules/tensorkrowch/embeddings.html index 7c5d2db..9f85a08 100644 --- a/docs/_build/html/_modules/tensorkrowch/embeddings.html +++ b/docs/_build/html/_modules/tensorkrowch/embeddings.html @@ -5,7 +5,7 @@ - tensorkrowch.embeddings — TensorKrowch 1.0.1 documentation + tensorkrowch.embeddings — TensorKrowch 1.1.0 documentation @@ -612,6 +612,18 @@

Source code for tensorkrowch.embeddings

     >>> emb_b = tk.embeddings.discretize(b, level=3)
     >>> emb_b.shape
     torch.Size([100, 5, 3])
+    
+    To embed a data tensor with elements between 0 and 1 as basis vectors, one
+    can concatenate :func:`discretize` with :func:`basis`.
+    
+    >>> a = torch.rand(100, 10)
+    >>> emb_a = tk.embeddings.discretize(a, level=1, base=5)
+    >>> emb_a.shape
+    torch.Size([100, 10, 1])
+    
+    >>> emb_a = tk.embeddings.basis(emb_a.squeeze(2).int(), dim=5)
+    >>> emb_a.shape
+    torch.Size([100, 10, 5])
     """
     if not isinstance(data, torch.Tensor):
         raise TypeError('`data` should be torch.Tensor type')
@@ -701,6 +713,18 @@ 

Source code for tensorkrowch.embeddings

     >>> emb_b = tk.embeddings.basis(b, dim=10)
     >>> emb_b.shape
     torch.Size([100, 5, 10])
+    
+    To embed a data tensor with elements between 0 and 1 as basis vectors, one
+    can concatenate :func:`discretize` with :func:`basis`.
+    
+    >>> a = torch.rand(100, 10)
+    >>> emb_a = tk.embeddings.discretize(a, level=1, base=5)
+    >>> emb_a.shape
+    torch.Size([100, 10, 1])
+    
+    >>> emb_a = tk.embeddings.basis(emb_a.squeeze(2).int(), dim=5)
+    >>> emb_a.shape
+    torch.Size([100, 10, 5])
     """
     if not isinstance(data, torch.Tensor):
         raise TypeError('`data` should be torch.Tensor type')
diff --git a/docs/_build/html/_modules/tensorkrowch/initializers.html b/docs/_build/html/_modules/tensorkrowch/initializers.html
index 23c7d52..138f9d0 100644
--- a/docs/_build/html/_modules/tensorkrowch/initializers.html
+++ b/docs/_build/html/_modules/tensorkrowch/initializers.html
@@ -5,7 +5,7 @@
   
     
     
-    tensorkrowch.initializers — TensorKrowch 1.0.1 documentation
+    tensorkrowch.initializers — TensorKrowch 1.1.0 documentation
     
   
   
@@ -300,303 +300,200 @@ 

Source code for tensorkrowch.initializers

 from tensorkrowch.components import TensorNetwork
 
 
-def _initializer(init_method,
+def _initializer(init_method: Text,
                  shape: Shape,
-                 axes_names: Optional[Sequence[Text]] = None,
-                 name: Optional[Text] = None,
-                 network: Optional[TensorNetwork] = None,
                  param_node: bool = False,
-                 device: Optional[torch.device] = None,
-                 **kwargs: float) -> AbstractNode:
+                 *args,
+                 **kwargs) -> AbstractNode:
     if not param_node:
-        return Node(shape=shape,
-                    axes_names=axes_names,
-                    name=name,
-                    network=network,
-                    init_method=init_method,
-                    device=device,
-                    **kwargs)
+        return Node(shape=shape, init_method=init_method, *args, **kwargs)
     else:
-        return ParamNode(shape=shape,
-                         axes_names=axes_names,
-                         name=name,
-                         network=network,
-                         init_method=init_method,
-                         device=device,
-                         **kwargs)
-
+        return ParamNode(shape=shape, init_method=init_method, *args, **kwargs)
+   
 
 
[docs]def empty(shape: Shape, - axes_names: Optional[Sequence[Text]] = None, - name: Optional[Text] = None, - network: Optional[TensorNetwork] = None, param_node: bool = False, - device: Optional[torch.device] = None) -> AbstractNode: - """ - Returns :class:`Node` or :class:`ParamNode` without tensor. - - Parameters - ---------- - shape : list[int], tuple[int] or torch.Size - Node's shape, that is, the shape of its tensor. - axes_names : list[str] or tuple[str], optional - Sequence of names for each of the node's axes. Names are used to access - the edge that is attached to the node in a certain axis. Hence, they - should be all distinct. - name : str, optional - Node's name, used to access the node from de :class:`TensorNetwork` - where it belongs. It cannot contain blank spaces. - network : TensorNetwork, optional - Tensor network where the node should belong. If None, a new tensor - network, will be created to contain the node. - param_node : bool - Boolean indicating whether the node should be a :class:`ParamNode` - (``True``) or a :class:`Node` (``False``). - device : torch.device, optional - Device where to initialize the tensor. - - Returns - ------- - Node or ParamNode - """ - return _initializer(None, - shape=shape, - axes_names=axes_names, - name=name, - network=network, - param_node=param_node, - device=device)
+ *args, + **kwargs) -> AbstractNode: + """ + Returns :class:`Node` or :class:`ParamNode` without tensor. + + Parameters + ---------- + shape : list[int], tuple[int] or torch.Size + Node's shape, that is, the shape of its tensor. + param_node : bool + Boolean indicating whether the node should be a :class:`ParamNode` + (``True``) or a :class:`Node` (``False``). + args : + Arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + kwargs : + Keyword arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + + Returns + ------- + Node or ParamNode + """ + return _initializer(None, + shape=shape, + param_node=param_node, + *args, + **kwargs)
[docs]def zeros(shape: Shape, - axes_names: Optional[Sequence[Text]] = None, - name: Optional[Text] = None, - network: Optional[TensorNetwork] = None, param_node: bool = False, - device: Optional[torch.device] = None) -> AbstractNode: - """ - Returns :class:`Node` or :class:`ParamNode` filled with zeros. - - Parameters - ---------- - shape : list[int], tuple[int] or torch.Size - Node's shape, that is, the shape of its tensor. - axes_names : list[str] or tuple[str], optional - Sequence of names for each of the node's axes. Names are used to access - the edge that is attached to the node in a certain axis. Hence, they - should be all distinct. - name : str, optional - Node's name, used to access the node from de :class:`TensorNetwork` - where it belongs. It cannot contain blank spaces. - network : TensorNetwork, optional - Tensor network where the node should belong. If None, a new tensor - network, will be created to contain the node. - param_node : bool - Boolean indicating whether the node should be a :class:`ParamNode` - (``True``) or a :class:`Node` (``False``). - device : torch.device, optional - Device where to initialize the tensor. - - Returns - ------- - Node or ParamNode - """ - return _initializer('zeros', - shape=shape, - axes_names=axes_names, - name=name, - network=network, - param_node=param_node, - device=device)
- - -
[docs]def ones(shape: Optional[Shape] = None, - axes_names: Optional[Sequence[Text]] = None, - name: Optional[Text] = None, - network: Optional[TensorNetwork] = None, + *args, + **kwargs) -> AbstractNode: + """ + Returns :class:`Node` or :class:`ParamNode` filled with zeros. + + Parameters + ---------- + shape : list[int], tuple[int] or torch.Size + Node's shape, that is, the shape of its tensor. + param_node : bool + Boolean indicating whether the node should be a :class:`ParamNode` + (``True``) or a :class:`Node` (``False``). + args : + Arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + kwargs : + Keyword arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + + Returns + ------- + Node or ParamNode + """ + return _initializer('zeros', + shape=shape, + param_node=param_node, + *args, + **kwargs)
+ + +
[docs]def ones(shape: Shape, param_node: bool = False, - device: Optional[torch.device] = None) -> AbstractNode: - """ - Returns :class:`Node` or :class:`ParamNode` filled with ones. - - Parameters - ---------- - shape : list[int], tuple[int] or torch.Size - Node's shape, that is, the shape of its tensor. - axes_names : list[str] or tuple[str], optional - Sequence of names for each of the node's axes. Names are used to access - the edge that is attached to the node in a certain axis. Hence, they - should be all distinct. - name : str, optional - Node's name, used to access the node from de :class:`TensorNetwork` - where it belongs. It cannot contain blank spaces. - network : TensorNetwork, optional - Tensor network where the node should belong. If None, a new tensor - network, will be created to contain the node. - param_node : bool - Boolean indicating whether the node should be a :class:`ParamNode` - (``True``) or a :class:`Node` (``False``). - device : torch.device, optional - Device where to initialize the tensor. - - Returns - ------- - Node or ParamNode - """ - return _initializer('ones', - shape=shape, - axes_names=axes_names, - name=name, - network=network, - param_node=param_node, - device=device)
- - -
[docs]def copy(shape: Optional[Shape] = None, - axes_names: Optional[Sequence[Text]] = None, - name: Optional[Text] = None, - network: Optional[TensorNetwork] = None, + *args, + **kwargs) -> AbstractNode: + """ + Returns :class:`Node` or :class:`ParamNode` filled with ones. + + Parameters + ---------- + shape : list[int], tuple[int] or torch.Size + Node's shape, that is, the shape of its tensor. + param_node : bool + Boolean indicating whether the node should be a :class:`ParamNode` + (``True``) or a :class:`Node` (``False``). + args : + Arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + kwargs : + Keyword arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + + Returns + ------- + Node or ParamNode + """ + return _initializer('ones', + shape=shape, + param_node=param_node, + *args, + **kwargs)
+ + +
[docs]def copy(shape: Shape, param_node: bool = False, - device: Optional[torch.device] = None) -> AbstractNode: - """ - Returns :class:`Node` or :class:`ParamNode` with a copy tensor, that is, - a tensor filled with zeros except in the diagonal (elements - :math:`T_{i_1 \ldots i_n}` with :math:`i_1 = \ldots = i_n`), which is - filled with ones. - - Parameters - ---------- - shape : list[int], tuple[int] or torch.Size - Node's shape, that is, the shape of its tensor. - axes_names : list[str] or tuple[str], optional - Sequence of names for each of the node's axes. Names are used to access - the edge that is attached to the node in a certain axis. Hence, they - should be all distinct. - name : str, optional - Node's name, used to access the node from de :class:`TensorNetwork` - where it belongs. It cannot contain blank spaces. - network : TensorNetwork, optional - Tensor network where the node should belong. If None, a new tensor - network, will be created to contain the node. - param_node : bool - Boolean indicating whether the node should be a :class:`ParamNode` - (``True``) or a :class:`Node` (``False``). - device : torch.device, optional - Device where to initialize the tensor. - - Returns - ------- - Node or ParamNode - """ - return _initializer('copy', - shape=shape, - axes_names=axes_names, - name=name, - network=network, - param_node=param_node, - device=device)
- - -
[docs]def rand(shape: Optional[Shape] = None, - axes_names: Optional[Sequence[Text]] = None, - name: Optional[Text] = None, - network: Optional[TensorNetwork] = None, + *args, + **kwargs) -> AbstractNode: + """ + Returns :class:`Node` or :class:`ParamNode` with a copy tensor, that is, + a tensor filled with zeros except in the diagonal (elements + :math:`T_{i_1 \ldots i_n}` with :math:`i_1 = \ldots = i_n`), which is + filled with ones. + + Parameters + ---------- + shape : list[int], tuple[int] or torch.Size + Node's shape, that is, the shape of its tensor. + param_node : bool + Boolean indicating whether the node should be a :class:`ParamNode` + (``True``) or a :class:`Node` (``False``). + args : + Arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + kwargs : + Keyword arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + + Returns + ------- + Node or ParamNode + """ + return _initializer('copy', + shape=shape, + param_node=param_node, + *args, + **kwargs)
+ + +
[docs]def rand(shape: Shape, param_node: bool = False, - device: Optional[torch.device] = None, - low: float = 0., - high: float = 1., ) -> AbstractNode: - """ - Returns :class:`Node` or :class:`ParamNode` filled with elements drawn from - a uniform distribution :math:`U(low, high)`. - - Parameters - ---------- - shape : list[int], tuple[int] or torch.Size - Node's shape, that is, the shape of its tensor. - axes_names : list[str] or tuple[str], optional - Sequence of names for each of the node's axes. Names are used to access - the edge that is attached to the node in a certain axis. Hence, they - should be all distinct. - name : str, optional - Node's name, used to access the node from de :class:`TensorNetwork` - where it belongs. It cannot contain blank spaces. - network : TensorNetwork, optional - Tensor network where the node should belong. If None, a new tensor - network, will be created to contain the node. - param_node : bool - Boolean indicating whether the node should be a :class:`ParamNode` - (``True``) or a :class:`Node` (``False``). - device : torch.device, optional - Device where to initialize the tensor. - low : float - Lower limit of the uniform distribution. - high : float - Upper limit of the uniform distribution. - - Returns - ------- - Node or ParamNode - """ - return _initializer('rand', - shape=shape, - axes_names=axes_names, - name=name, - network=network, - param_node=param_node, - device=device, - low=low, - high=high)
- - -
[docs]def randn(shape: Optional[Shape] = None, - axes_names: Optional[Sequence[Text]] = None, - name: Optional[Text] = None, - network: Optional[TensorNetwork] = None, + *args, + **kwargs) -> AbstractNode: + """ + Returns :class:`Node` or :class:`ParamNode` filled with elements drawn from + a uniform distribution :math:`U(low, high)`. + + Parameters + ---------- + shape : list[int], tuple[int] or torch.Size + Node's shape, that is, the shape of its tensor. + param_node : bool + Boolean indicating whether the node should be a :class:`ParamNode` + (``True``) or a :class:`Node` (``False``). + args : + Arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + kwargs : + Keyword arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + + Returns + ------- + Node or ParamNode + """ + return _initializer('rand', + shape=shape, + param_node=param_node, + *args, + **kwargs)
+ + +
[docs]def randn(shape: Shape, param_node: bool = False, - device: Optional[torch.device] = None, - mean: float = 0., - std: float = 1., ) -> AbstractNode: - """ - Returns :class:`Node` or :class:`ParamNode` filled with elements drawn from - a normal distribution :math:`N(mean, std)`. - - Parameters - ---------- - shape : list[int], tuple[int] or torch.Size - Node's shape, that is, the shape of its tensor. - axes_names : list[str] or tuple[str], optional - Sequence of names for each of the node's axes. Names are used to access - the edge that is attached to the node in a certain axis. Hence, they - should be all distinct. - name : str, optional - Node's name, used to access the node from de :class:`TensorNetwork` - where it belongs. It cannot contain blank spaces. - network : TensorNetwork, optional - Tensor network where the node should belong. If None, a new tensor - network, will be created to contain the node. - param_node : bool - Boolean indicating whether the node should be a :class:`ParamNode` - (``True``) or a :class:`Node` (``False``). - device : torch.device, optional - Device where to initialize the tensor. - mean : float - Mean of the normal distribution. - std : float - Standard deviation of the normal distribution. - - Returns - ------- - Node or ParamNode - """ - return _initializer('randn', - shape=shape, - axes_names=axes_names, - name=name, - network=network, - param_node=param_node, - device=device, - mean=mean, - std=std)
+ *args, + **kwargs) -> AbstractNode: + """ + Returns :class:`Node` or :class:`ParamNode` filled with elements drawn from + a normal distribution :math:`N(mean, std)`. + + Parameters + ---------- + shape : list[int], tuple[int] or torch.Size + Node's shape, that is, the shape of its tensor. + param_node : bool + Boolean indicating whether the node should be a :class:`ParamNode` + (``True``) or a :class:`Node` (``False``). + args : + Arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + kwargs : + Keyword arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + + Returns + ------- + Node or ParamNode + """ + return _initializer('randn', + shape=shape, + param_node=param_node, + *args, + **kwargs)
diff --git a/docs/_build/html/_modules/tensorkrowch/models/mpo.html b/docs/_build/html/_modules/tensorkrowch/models/mpo.html index 0ccc6ef..32b0487 100644 --- a/docs/_build/html/_modules/tensorkrowch/models/mpo.html +++ b/docs/_build/html/_modules/tensorkrowch/models/mpo.html @@ -5,7 +5,7 @@ - tensorkrowch.models.mpo — TensorKrowch 1.0.1 documentation + tensorkrowch.models.mpo — TensorKrowch 1.1.0 documentation @@ -297,6 +297,8 @@

Source code for tensorkrowch.models.mpo

 from typing import (List, Optional, Sequence,
                     Text, Tuple, Union)
 
+from math import sqrt
+
 import torch
 
 import tensorkrowch.operations as op
@@ -700,10 +702,6 @@ 

Source code for tensorkrowch.models.mpo

             Keyword arguments for the different initialization methods. See
             :meth:`~tensorkrowch.AbstractNode.make_tensor`.
         """
-        if self._boundary == 'obc':
-            self._left_node.set_tensor(init_method='copy', device=device)
-            self._right_node.set_tensor(init_method='copy', device=device)
-
         if tensors is not None:
             if len(tensors) != self._n_features:
                 raise ValueError('`tensors` should be a sequence of `n_features`'
@@ -711,6 +709,10 @@ 

Source code for tensorkrowch.models.mpo

             
             if self._boundary == 'obc':
                 tensors = tensors[:]
+                
+                if device is None:
+                    device = tensors[0].device
+                
                 if len(tensors) == 1:
                     tensors[0] = tensors[0].reshape(1,
                                                     tensors[0].shape[0],
@@ -720,13 +722,13 @@ 

Source code for tensorkrowch.models.mpo

                 else:
                     # Left node
                     aux_tensor = torch.zeros(*self._mats_env[0].shape,
-                                             device=tensors[0].device)
+                                             device=device)
                     aux_tensor[0] = tensors[0]
                     tensors[0] = aux_tensor
                     
                     # Right node
                     aux_tensor = torch.zeros(*self._mats_env[-1].shape,
-                                             device=tensors[-1].device)
+                                             device=device)
                     aux_tensor[..., 0, :] = tensors[-1]
                     tensors[-1] = aux_tensor
                 
@@ -748,7 +750,11 @@ 

Source code for tensorkrowch.models.mpo

                     elif i == (self._n_features - 1):
                         # Right node
                         aux_tensor[..., 0, :] = node.tensor[..., 0, :]
-                    node.tensor = aux_tensor
+ node.tensor = aux_tensor + + if self._boundary == 'obc': + self._left_node.set_tensor(init_method='copy', device=device) + self._right_node.set_tensor(init_method='copy', device=device)
[docs] def set_data_nodes(self) -> None: """ @@ -832,20 +838,23 @@

Source code for tensorkrowch.models.mpo

         return net
def _input_contraction(self, - nodes_env: List[Node], + nodes_env: List[AbstractNode], + input_nodes: List[AbstractNode], inline_input: bool = False) -> Tuple[ Optional[List[Node]], Optional[List[Node]]]: """Contracts input data nodes with MPO nodes.""" if inline_input: - mats_result = [node['input'].contract() for node in nodes_env] + mats_result = [ + in_node @ node + for node, in_node in zip(nodes_env, input_nodes) + ] return mats_result else: if nodes_env: stack = op.stack(nodes_env) - stack_data = op.stack( - [node.neighbours('input') for node in nodes_env]) + stack_data = op.stack(input_nodes) stack ^ stack_data @@ -856,15 +865,26 @@

Source code for tensorkrowch.models.mpo

                 return []
 
     @staticmethod
-    def _inline_contraction(nodes: List[Node]) -> Node:
+    def _inline_contraction(mats_env: List[AbstractNode],
+                            renormalize: bool = False) -> Node:
         """Contracts sequence of MPO nodes (matrices) inline."""
-        result_node = nodes[0]
-        for node in nodes[1:]:
+        result_node = mats_env[0]
+        for node in mats_env[1:]:
             result_node @= node
+            
+            if renormalize:
+                right_axes = []
+                for ax_name in result_node.axes_names:
+                    if 'right' in ax_name:
+                        right_axes.append(ax_name)
+                if right_axes:
+                    result_node = result_node.renormalize(axis=right_axes)
+            
         return result_node
 
     def _contract_envs_inline(self,
-                              mats_env: List[Node],
+                              mats_env: List[AbstractNode],
+                              renormalize: bool = False,
                               mps: Optional[MPSData] = None) -> Node:
         """Contracts nodes environments inline."""
         if (mps is not None) and (mps._boundary == 'obc'):
@@ -874,13 +894,16 @@ 

Source code for tensorkrowch.models.mpo

         if self._boundary == 'obc':
             mats_env = [self._left_node] + mats_env
             mats_env = mats_env + [self._right_node]
-        return self._inline_contraction(mats_env)
+        return self._inline_contraction(mats_env=mats_env,
+                                        renormalize=renormalize)
 
-    def _aux_pairwise(self, nodes: List[Node]) -> Tuple[List[Node],
+    def _aux_pairwise(self,
+                      mats_env: List[AbstractNode],
+                      renormalize: bool = False) -> Tuple[List[Node],
     List[Node]]:
         """Contracts a sequence of MPO nodes (matrices) pairwise."""
-        length = len(nodes)
-        aux_nodes = nodes
+        length = len(mats_env)
+        aux_nodes = mats_env
         if length > 1:
             half_length = length // 2
             nice_length = 2 * half_length
@@ -896,32 +919,48 @@ 

Source code for tensorkrowch.models.mpo

 
             aux_nodes = stack1 @ stack2
             aux_nodes = op.unbind(aux_nodes)
+            
+            if renormalize:
+                for i in range(len(aux_nodes)):
+                    axes = []
+                    for ax_name in aux_nodes[i].axes_names:
+                        if ('left' in ax_name) or ('right' in ax_name):
+                            axes.append(ax_name)
+                    if axes:
+                        aux_nodes[i] = aux_nodes[i].renormalize(axis=axes)
 
             return aux_nodes, leftover
-        return nodes, []
+        return mats_env, []
 
     def _pairwise_contraction(self,
-                              mats_nodes: List[Node],
-                              mps: Optional[MPSData] = None) -> Node:
+                              mats_env: List[Node],
+                              mps: Optional[MPSData] = None,
+                              renormalize: bool = False) -> Node:
         """Contracts nodes environments pairwise."""
-        length = len(mats_nodes)
-        aux_nodes = mats_nodes
+        length = len(mats_env)
+        aux_nodes = mats_env
         if length > 1:
             leftovers = []
             while length > 1:
-                aux1, aux2 = self._aux_pairwise(aux_nodes)
+                aux1, aux2 = self._aux_pairwise(mats_env=aux_nodes,
+                                                renormalize=renormalize)
                 aux_nodes = aux1
                 leftovers = aux2 + leftovers
                 length = len(aux1)
 
             aux_nodes = aux_nodes + leftovers
-            return self._pairwise_contraction(aux_nodes, mps)
+            return self._pairwise_contraction(mats_env=aux_nodes,
+                                              renormalize=renormalize,
+                                              mps=mps)
 
-        return self._contract_envs_inline(aux_nodes, mps)
+        return self._contract_envs_inline(mats_env=aux_nodes,
+                                          renormalize=renormalize,
+                                          mps=mps)
     
 
[docs] def contract(self, inline_input: bool = False, inline_mats: bool = False, + renormalize: bool = False, mps: Optional[MPSData] = None) -> Node: """ Contracts the whole MPO with input data nodes. The input can be in the @@ -963,6 +1002,14 @@

Source code for tensorkrowch.models.mpo

             Boolean indicating whether the sequence of matrices (resultant
             after contracting the input ``data`` nodes) should be contracted
             inline or as a sequence of pairwise stacked contrations.
+        renormalize : bool
+            Indicates whether nodes should be renormalized after contraction.
+            If not, it may happen that the norm explodes or vanishes, as it
+            is being accumulated from all nodes. Renormalization aims to avoid
+            this undesired behavior by extracting the norm of each node on a
+            logarithmic scale. The renormalization only occurs when multiplying
+            sequences of matrices, once the `input` contractions have been
+            already performed, including contracting against ``MPSData``.
         mps : MPSData, optional
             MPS that is to be contracted with the MPO. New data can be
             put into the MPS via :meth:`MPSData.add_data`, and the MPS-MPO
@@ -988,12 +1035,19 @@ 

Source code for tensorkrowch.models.mpo

             for mps_node, mpo_node in zip(mps._mats_env, self._mats_env):
                 mps_node['feature'] ^ mpo_node['input']
                 
-        mats_env = self._input_contraction(self._mats_env, inline_input)
+        mats_env = self._input_contraction(
+            nodes_env=self._mats_env,
+            input_nodes=[node.neighbours('input') for node in self._mats_env],
+            inline_input=inline_input)
         
         if inline_mats:
-            result = self._contract_envs_inline(mats_env, mps)
+            result = self._contract_envs_inline(mats_env=mats_env,
+                                                renormalize=renormalize,
+                                                mps=mps)
         else:
-            result = self._pairwise_contraction(mats_env, mps)
+            result = self._pairwise_contraction(mats_env=mats_env,
+                                                renormalize=renormalize,
+                                                mps=mps)
             
         # Contract periodic edge
         if result.is_connected_to(result):
@@ -1012,7 +1066,172 @@ 

Source code for tensorkrowch.models.mpo

         if all_edges != list(range(len(all_edges))):
             result = op.permute(result, tuple(all_edges))
         
-        return result
+ return result
+ +
[docs] @torch.no_grad() + def canonicalize(self, + oc: Optional[int] = None, + mode: Text = 'svd', + rank: Optional[int] = None, + cum_percentage: Optional[float] = None, + cutoff: Optional[float] = None, + renormalize: bool = False) -> None: + r""" + Turns MPO into `canonical` form via local SVD/QR decompositions in the + same way this transformation is applied to :class:`~tensorkrowch.models.MPS`. + + To specify the new bond dimensions, the arguments ``rank``, + ``cum_percentage`` or ``cutoff`` can be specified. These will be used + equally for all SVD computations. + + If none of them are specified, the bond dimensions won't be modified + if possible. Only when the bond dimension is bigger than the physical + dimension multiplied by the other bond dimension of the node, it will + be cropped to that size. + + Parameters + ---------- + oc : int + Position of the orthogonality center. It should be between 0 and + ``n_features - 1``. + mode : {"svd", "svdr", "qr"} + Indicates which decomposition should be used to split a node after + contracting it. See more at :func:`~tensorkrowch.svd_`, + :func:`~tensorkrowch.svdr_`, :func:`~tensorkrowch.qr_`. + If mode is "qr", operation :func:`~tensorkrowch.qr_` will be + performed on nodes at the left of the output node, whilst operation + :func:`~tensorkrowch.rq_` will be used for nodes at the right. + rank : int, optional + Number of singular values to keep. + cum_percentage : float, optional + Proportion that should be satisfied between the sum of all singular + values kept and the total sum of all singular values. + + .. math:: + + \frac{\sum_{i \in \{kept\}}{s_i}}{\sum_{i \in \{all\}}{s_i}} \ge + cum\_percentage + cutoff : float, optional + Quantity that lower bounds singular values in order to be kept. + renormalize : bool + Indicates whether nodes should be renormalized after SVD/QR + decompositions. If not, it may happen that the norm explodes as it + is being accumulated from all nodes. Renormalization aims to avoid + this undesired behavior by extracting the norm of each node on a + logarithmic scale after SVD/QR decompositions are computed. Finally, + the normalization factor is evenly distributed among all nodes of + the MPO. + + Examples + -------- + >>> mpo = tk.models.MPO(n_features=4, + ... in_dim=2, + ... out_dim=2, + ... bond_dim=5) + >>> mpo.canonicalize(rank=3) + >>> mpo.bond_dim + [3, 3, 3] + """ + self.reset() + + prev_auto_stack = self._auto_stack + self.auto_stack = False + + if oc is None: + oc = self._n_features - 1 + elif (oc < 0) or (oc >= self._n_features): + raise ValueError('Orthogonality center position `oc` should be ' + 'between 0 and `n_features` - 1') + + log_norm = 0 + + nodes = self._mats_env[:] + if self._boundary == 'obc': + nodes[0].tensor[1:] = torch.zeros_like( + nodes[0].tensor[1:]) + nodes[-1].tensor[..., 1:, :] = torch.zeros_like( + nodes[-1].tensor[..., 1:, :]) + + # If mode is svd or svr and none of the args is provided, the ranks are + # kept as they were originally + keep_rank = False + if (rank is None) and (cum_percentage is None) and (cutoff is None): + keep_rank = True + + for i in range(oc): + if mode == 'svd': + result1, result2 = nodes[i]['right'].svd_( + side='right', + rank=nodes[i]['right'].size() if keep_rank else rank, + cum_percentage=cum_percentage, + cutoff=cutoff) + elif mode == 'svdr': + result1, result2 = nodes[i]['right'].svdr_( + side='right', + rank=nodes[i]['right'].size() if keep_rank else rank, + cum_percentage=cum_percentage, + cutoff=cutoff) + elif mode == 'qr': + result1, result2 = nodes[i]['right'].qr_() + else: + raise ValueError('`mode` can only be "svd", "svdr" or "qr"') + + if renormalize: + aux_norm = result2.norm() / sqrt(result2.shape[0]) + if not aux_norm.isinf() and (aux_norm > 0): + result2.tensor = result2.tensor / aux_norm + log_norm += aux_norm.log() + + result1 = result1.parameterize() + nodes[i] = result1 + nodes[i + 1] = result2 + + for i in range(len(nodes) - 1, oc, -1): + if mode == 'svd': + result1, result2 = nodes[i]['left'].svd_( + side='left', + rank=nodes[i]['left'].size() if keep_rank else rank, + cum_percentage=cum_percentage, + cutoff=cutoff) + elif mode == 'svdr': + result1, result2 = nodes[i]['left'].svdr_( + side='left', + rank=nodes[i]['left'].size() if keep_rank else rank, + cum_percentage=cum_percentage, + cutoff=cutoff) + elif mode == 'qr': + result1, result2 = nodes[i]['left'].rq_() + else: + raise ValueError('`mode` can only be "svd", "svdr" or "qr"') + + if renormalize: + aux_norm = result1.norm() / sqrt(result1.shape[0]) + if not aux_norm.isinf() and (aux_norm > 0): + result1.tensor = result1.tensor / aux_norm + log_norm += aux_norm.log() + + result2 = result2.parameterize() + nodes[i] = result2 + nodes[i - 1] = result1 + + nodes[oc] = nodes[oc].parameterize() + + # Rescale + if log_norm != 0: + rescale = (log_norm / len(nodes)).exp() + + if renormalize and (log_norm != 0): + for node in nodes: + node.tensor = node.tensor * rescale + + # Update variables + if self._boundary == 'obc': + self._bond_dim = [node['right'].size() for node in nodes[:-1]] + else: + self._bond_dim = [node['right'].size() for node in nodes] + self._mats_env = nodes + + self.auto_stack = prev_auto_stack
[docs]class UMPO(MPO): # MARK: UMPO @@ -1174,8 +1393,6 @@

Source code for tensorkrowch.models.mpo

             Keyword arguments for the different initialization methods. See
             :meth:`~tensorkrowch.AbstractNode.make_tensor`.
         """
-        node = self.uniform_memory
-        
         if tensors is not None:
             self.uniform_memory.tensor = tensors[0]
         
@@ -1255,7 +1472,18 @@ 

Source code for tensorkrowch.models.mpo

         for node in net._mats_env:
             node.set_tensor_from(net.uniform_memory)
             
-        return net
+ return net
+ + def canonicalize(self, + oc: Optional[int] = None, + mode: Text = 'svd', + rank: Optional[int] = None, + cum_percentage: Optional[float] = None, + cutoff: Optional[float] = None, + renormalize: bool = False) -> None: + """:meta private:""" + raise NotImplementedError( + '`canonicalize` not implemented for UMPO')
diff --git a/docs/_build/html/_modules/tensorkrowch/models/mps.html b/docs/_build/html/_modules/tensorkrowch/models/mps.html index 4488acf..d837617 100644 --- a/docs/_build/html/_modules/tensorkrowch/models/mps.html +++ b/docs/_build/html/_modules/tensorkrowch/models/mps.html @@ -5,7 +5,7 @@ - tensorkrowch.models.mps — TensorKrowch 1.0.1 documentation + tensorkrowch.models.mps — TensorKrowch 1.1.0 documentation @@ -855,35 +855,88 @@

Source code for tensorkrowch.models.mps

                 if i == self._n_features - 1:
                     self._mats_env[-1]['right'] ^ self._right_node['left']
     
-    def _make_unitaries(self, device: Optional[torch.device] = None) -> List[torch.Tensor]:
+    def _make_canonical(self, device: Optional[torch.device] = None) -> List[torch.Tensor]:
         """
         Creates random unitaries to initialize the MPS in canonical form with
-        orthogonality center at the rightmost node."""
+        orthogonality center at the rightmost node. Unitaries in nodes are
+        scaled so that the total norm squared of the initial MPS is the product
+        of all the physical dimensions.
+        """
         tensors = []
         for i, node in enumerate(self._mats_env):
             if self._boundary == 'obc':
                 if i == 0:
                     node_shape = node.shape[1:]
                     aux_shape = node_shape
+                    phys_dim = node_shape[0]
                 elif i == (self._n_features - 1):
                     node_shape = node.shape[:2]
                     aux_shape = node_shape
+                    phys_dim = node_shape[1]
                 else:
                     node_shape = node.shape
                     aux_shape = (node.shape[:2].numel(), node.shape[2])
+                    phys_dim = node_shape[1]
             else:
                 node_shape = node.shape
                 aux_shape = (node.shape[:2].numel(), node.shape[2])
+                phys_dim = node_shape[1]
             size = max(aux_shape[0], aux_shape[1])
             
             tensor = random_unitary(size, device=device)
             tensor = tensor[:min(aux_shape[0], size), :min(aux_shape[1], size)]
             tensor = tensor.reshape(*node_shape)
             
+            if i == (self._n_features - 1):
+                if self._boundary == 'obc':
+                    tensor = tensor.t() / tensor.norm() * sqrt(phys_dim)
+                else:
+                    tensor = tensor / tensor.norm() * sqrt(phys_dim)
+            else:
+                tensor = tensor * sqrt(phys_dim)
+            
             tensors.append(tensor)
-        
-        if self._boundary == 'obc':
-            tensors[-1] = tensors[-1] / tensors[-1].norm()
+        return tensors
+    
+    def _make_unitaries(self, device: Optional[torch.device] = None) -> List[torch.Tensor]:
+        """
+        Creates random unitaries to initialize the MPS nodes as stacks of
+        unitaries.
+        """
+        tensors = []
+        for i, node in enumerate(self._mats_env):
+            units = []
+            size = max(node.shape[0], node.shape[2])
+            if self._boundary == 'obc':
+                if i == 0:
+                    size_1 = 1
+                    size_2 = min(node.shape[2], size)
+                elif i == (self._n_features - 1):
+                    size_1 = min(node.shape[0], size)
+                    size_2 = 1
+                else:
+                    size_1 = min(node.shape[0], size)
+                    size_2 = min(node.shape[2], size)
+            else:
+                size_1 = min(node.shape[0], size)
+                size_2 = min(node.shape[2], size)
+            
+            for _ in range(node.shape[1]):
+                tensor = random_unitary(size, device=device)
+                tensor = tensor[:size_1, :size_2]
+                units.append(tensor)
+            
+            units = torch.stack(units, dim=1)
+            
+            if self._boundary == 'obc':
+                if i == 0:
+                    tensors.append(units.squeeze(0))
+                elif i == (self._n_features - 1):
+                    tensors.append(units.squeeze(-1))
+                else:
+                    tensors.append(units)
+            else:    
+                tensors.append(units)
         return tensors
 
 
[docs] def initialize(self, @@ -906,9 +959,16 @@

Source code for tensorkrowch.models.mps

           top of random gaussian tensors. In this case, ``std`` should be
           specified with a low value, e.g., ``std = 1e-9``.
         
-        * ``"unit"``: Nodes are initialized as random unitaries, so that the
-          MPS is in canonical form, with the orthogonality center at the
-          rightmost node.
+        * ``"unit"``: Nodes are initialized as stacks of random unitaries. This,
+          combined (at least) with an embedding of the inputs as elements of
+          the computational basis (:func:`~tensorkrowch.embeddings.discretize`
+          combined with :func:`~tensorkrowch.embeddings.basis`)
+        
+        * ``"canonical"```: MPS is initialized in canonical form with a squared
+          norm `close` to the product of all the physical dimensions (if bond
+          dimensions are bigger than the powers of the physical dimensions,
+          the norm could vary). Th orthogonality center is at the rightmost
+          node.
         
         Parameters
         ----------
@@ -918,7 +978,7 @@ 

Source code for tensorkrowch.models.mps

             last ones, which can be rank-2, or rank-1 (if the first and last are
             the same). If ``boundary`` is ``"pbc"``, all tensors should be
             rank-3.
-        init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit"}, optional
+        init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit", "canonical"}, optional
             Initialization method.
         device : torch.device, optional
             Device where to initialize the tensors if ``init_method`` is provided.
@@ -926,32 +986,34 @@ 

Source code for tensorkrowch.models.mps

             Keyword arguments for the different initialization methods. See
             :meth:`~tensorkrowch.AbstractNode.make_tensor`.
         """
-        if self._boundary == 'obc':
-            self._left_node.set_tensor(init_method='copy', device=device)
-            self._right_node.set_tensor(init_method='copy', device=device)
-        
         if init_method == 'unit':
             tensors = self._make_unitaries(device=device)
+        elif init_method == 'canonical':
+            tensors = self._make_canonical(device=device)
 
         if tensors is not None:
             if len(tensors) != self._n_features:
-                raise ValueError('`tensors` should be a sequence of `n_features`'
-                                 ' elements')
+                raise ValueError(
+                    '`tensors` should be a sequence of `n_features` elements')
             
             if self._boundary == 'obc':
                 tensors = tensors[:]
+                
+                if device is None:
+                    device = tensors[0].device
+                
                 if len(tensors) == 1:
                     tensors[0] = tensors[0].reshape(1, -1, 1)
                 else:
                     # Left node
                     aux_tensor = torch.zeros(*self._mats_env[0].shape,
-                                             device=tensors[0].device)
+                                             device=device)
                     aux_tensor[0] = tensors[0]
                     tensors[0] = aux_tensor
                     
                     # Right node
                     aux_tensor = torch.zeros(*self._mats_env[-1].shape,
-                                             device=tensors[-1].device)
+                                             device=device)
                     aux_tensor[..., 0] = tensors[-1]
                     tensors[-1] = aux_tensor
                 
@@ -984,7 +1046,11 @@ 

Source code for tensorkrowch.models.mps

                     elif i == (self._n_features - 1):
                         # Right node
                         aux_tensor[..., 0] = node.tensor[..., 0]
-                        node.tensor = aux_tensor
+ node.tensor = aux_tensor + + if self._boundary == 'obc': + self._left_node.set_tensor(init_method='copy', device=device) + self._right_node.set_tensor(init_method='copy', device=device)
[docs] def set_data_nodes(self) -> None: """ @@ -1076,7 +1142,7 @@

Source code for tensorkrowch.models.mps

         """Contracts input data nodes with MPS input nodes."""
         if inline_input:
             mats_result = [
-                node @ in_node
+                in_node @ node
                 for node, in_node in zip(nodes_env, input_nodes)
                 ]
             return mats_result
@@ -1096,20 +1162,42 @@ 

Source code for tensorkrowch.models.mps

 
     @staticmethod
     def _inline_contraction(mats_env: List[AbstractNode],
+                            renormalize: bool = False,
                             from_left: bool = True) -> Node:
         """Contracts sequence of MPS nodes (matrices) inline."""
         if from_left:
             result_node = mats_env[0]
             for node in mats_env[1:]:
                 result_node @= node
+                
+                if renormalize:
+                    right_axes = []
+                    for ax_name in result_node.axes_names:
+                        if 'right' in ax_name:
+                            right_axes.append(ax_name)
+                    if right_axes:
+                        result_node = result_node.renormalize(axis=right_axes)
+            
             return result_node
+        
         else:
             result_node = mats_env[-1]
             for node in mats_env[-2::-1]:
                 result_node = node @ result_node
+                
+                if renormalize:
+                    left_axes = []
+                    for ax_name in result_node.axes_names:
+                        if 'left' in ax_name:
+                            left_axes.append(ax_name)
+                    if left_axes:
+                        result_node = result_node.renormalize(axis=left_axes)
+            
             return result_node
 
-    def _contract_envs_inline(self, mats_env: List[AbstractNode]) -> Node:
+    def _contract_envs_inline(self,
+                              mats_env: List[AbstractNode],
+                              renormalize: bool = False) -> Node:
         """Contracts nodes environments inline."""
         from_left = True
         if self._boundary == 'obc':
@@ -1118,13 +1206,17 @@ 

Source code for tensorkrowch.models.mps

             if mats_env[-1].neighbours('right') is self._right_node:
                 mats_env = mats_env + [self._right_node]
                 from_left = False
-        return self._inline_contraction(mats_env=mats_env, from_left=from_left)
+        return self._inline_contraction(mats_env=mats_env,
+                                        renormalize=renormalize,
+                                        from_left=from_left)
 
-    def _aux_pairwise(self, nodes: List[AbstractNode]) -> Tuple[List[Node],
+    def _aux_pairwise(self,
+                      mats_env: List[AbstractNode],
+                      renormalize: bool = False) -> Tuple[List[Node],
     List[Node]]:
         """Contracts a sequence of MPS nodes (matrices) pairwise."""
-        length = len(nodes)
-        aux_nodes = nodes
+        length = len(mats_env)
+        aux_nodes = mats_env
         if length > 1:
             half_length = length // 2
             nice_length = 2 * half_length
@@ -1140,30 +1232,45 @@ 

Source code for tensorkrowch.models.mps

 
             aux_nodes = stack1 @ stack2
             aux_nodes = op.unbind(aux_nodes)
+            
+            if renormalize:
+                for i in range(len(aux_nodes)):
+                    axes = []
+                    for ax_name in aux_nodes[i].axes_names:
+                        if ('left' in ax_name) or ('right' in ax_name):
+                            axes.append(ax_name)
+                    if axes:
+                        aux_nodes[i] = aux_nodes[i].renormalize(axis=axes)
 
             return aux_nodes, leftover
-        return nodes, []
+        return mats_env, []
 
-    def _pairwise_contraction(self, mats_env: List[AbstractNode]) -> Node:
+    def _pairwise_contraction(self,
+                              mats_env: List[AbstractNode],
+                              renormalize: bool = False) -> Node:
         """Contracts nodes environments pairwise."""
         length = len(mats_env)
         aux_nodes = mats_env
         if length > 1:
             leftovers = []
             while length > 1:
-                aux1, aux2 = self._aux_pairwise(aux_nodes)
+                aux1, aux2 = self._aux_pairwise(mats_env=aux_nodes,
+                                                renormalize=renormalize)
                 aux_nodes = aux1
                 leftovers = aux2 + leftovers
                 length = len(aux1)
 
             aux_nodes = aux_nodes + leftovers
-            return self._pairwise_contraction(aux_nodes)
+            return self._pairwise_contraction(mats_env=aux_nodes,
+                                              renormalize=renormalize)
 
-        return self._contract_envs_inline(aux_nodes)
+        return self._contract_envs_inline(mats_env=aux_nodes,
+                                          renormalize=renormalize)
 
 
[docs] def contract(self, inline_input: bool = False, inline_mats: bool = False, + renormalize: bool = False, marginalize_output: bool = False, embedding_matrices: Optional[ Union[torch.Tensor, @@ -1225,6 +1332,15 @@

Source code for tensorkrowch.models.mps

             Boolean indicating whether the sequence of matrices (resultant
             after contracting the input ``data`` nodes) should be contracted
             inline or as a sequence of pairwise stacked contrations.
+        renormalize : bool
+            Indicates whether nodes should be renormalized after contraction.
+            If not, it may happen that the norm explodes or vanishes, as it
+            is being accumulated from all nodes. Renormalization aims to avoid
+            this undesired behavior by extracting the norm of each node on a
+            logarithmic scale. The renormalization only occurs when multiplying
+            sequences of matrices, once the `input` contractions have been
+            already performed, including contracting against embedding matrices
+            or MPOs when ``marginalize_output = True``.
         marginalize_output : bool
             Boolean indicating whether output nodes should be marginalized. If
             ``True``, after contracting all the input nodes with their
@@ -1291,14 +1407,31 @@ 

Source code for tensorkrowch.models.mps

             input_nodes=[node.neighbours('input') for node in self.in_env],
             inline_input=inline_input)
         
+        # NOTE: to leave the input edges open and marginalize output
+        # data_nodes = []
+        # for node in self.in_env:
+        #     data_node = node.neighbours('input')
+        #     if data_node:
+        #         data_nodes.append(data_node)
+        
+        # if data_nodes:
+        #     mats_in_env = self._input_contraction(
+        #         nodes_env=self.in_env,
+        #         input_nodes=data_nodes,
+        #         inline_input=inline_input)
+        # else:
+        #     mats_in_env = self.in_env
+        
         in_results = []
         for region in in_regions:      
             if inline_mats:
                 result = self._contract_envs_inline(
-                    mats_env=mats_in_env[:len(region)])
+                    mats_env=mats_in_env[:len(region)],
+                    renormalize=renormalize)
             else:
                 result = self._pairwise_contraction(
-                    mats_env=mats_in_env[:len(region)])
+                    mats_env=mats_in_env[:len(region)],
+                    renormalize=renormalize)
             
             mats_in_env = mats_in_env[len(region):]
             in_results.append(result)
@@ -1332,7 +1465,8 @@ 

Source code for tensorkrowch.models.mps

             
             if not marginalize_output:
                 # Contract all output nodes sequentially
-                result = self._inline_contraction(mats_env=nodes_out_env)
+                result = self._inline_contraction(mats_env=nodes_out_env,
+                                                  renormalize=renormalize)
             
             else:
                 # Copy output nodes sharing tensors
@@ -1437,7 +1571,8 @@ 

Source code for tensorkrowch.models.mps

                     inline_input=True)
                 
                 # Contract resultant matrices
-                result = self._inline_contraction(mats_env=mats_out_env)
+                result = self._inline_contraction(mats_env=mats_out_env,
+                                                  renormalize=renormalize)
             
         # Contract periodic edge
         if result.is_connected_to(result):
@@ -1478,10 +1613,12 @@ 

Source code for tensorkrowch.models.mps

         result = result.sqrt()
         return result
-
[docs] def partial_density(self, trace_sites: Sequence[int] = []) -> torch.Tensor: - """ +
[docs] def partial_density(self, + trace_sites: Sequence[int] = [], + renormalize: bool = True) -> torch.Tensor: + r""" Returns de partial density matrix, tracing out the sites specified - by ``trace_sites``. + by ``trace_sites``: :math:`\rho_A`. This method internally sets ``out_features = trace_sites``, and calls the :meth:`~tensorkrowch.TensorNetwork.forward` method with @@ -1505,6 +1642,14 @@

Source code for tensorkrowch.models.mps

             nodes that should be traced to compute the density matrix. If
             it is empty ``[]``, the total density matrix will be returned,
             though this may be costly if :attr:`n_features` is big.
+        renormalize : bool
+            Indicates whether nodes should be renormalized after contraction.
+            If not, it may happen that the norm explodes or vanishes, as it
+            is being accumulated from all nodes. Renormalization aims to avoid
+            this undesired behavior by extracting the norm of each node on a
+            logarithmic scale. The renormalization only occurs when multiplying
+            sequences of matrices, once the `input` contractions have been
+            already performed.
         
         Examples
         --------
@@ -1556,37 +1701,48 @@ 

Source code for tensorkrowch.models.mps

                     ]
             
             self.n_batches = len(dims)
-            result = self.forward(data, marginalize_output=True)
+            result = self.forward(data,
+                                  renormalize=renormalize,
+                                  marginalize_output=True)
             
         else:
-            result = self.forward(marginalize_output=True)
+            result = self.forward(renormalize=renormalize,
+                                  marginalize_output=True)
         
         return result
-
[docs] @torch.no_grad() - def mi(self, - middle_site: int, - renormalize: bool = False) -> Union[float, Tuple[float]]: +
[docs] @torch.no_grad() + def entropy(self, + middle_site: int, + renormalize: bool = False) -> Union[float, Tuple[float]]: r""" - Computes the Mutual Information between subsystems :math:`A` and - :math:`B`, :math:`\textrm{MI}(A:B)`, where :math:`A` goes from site + Computes the reduced von Neumann Entropy between subsystems :math:`A` + and :math:`B`, :math:`S(\rho_A)`, where :math:`A` goes from site 0 to ``middle_site``, and :math:`B` goes from ``middle_site + 1`` to ``n_features - 1``. - To compute the mutual information, the MPS is put into canonical form + To compute the reduced entropy, the MPS is put into canonical form with orthogonality center at ``middle_site``. Bond dimensions are not changed if possible. Only when the bond dimension is bigger than the physical dimension multiplied by the other bond dimension of the node, it will be cropped to that size. If the MPS is not normalized, it may happen that the computation of the - mutual information fails due to errors in the Singular Value + reduced entropy fails due to errors in the Singular Value Decompositions. To avoid this, it is recommended to set ``renormalize = True``. In this case, the norm of each node after the SVD is extracted in logarithmic form, and accumulated. As a result, - the function will return the tuple ``(mi, log_norm)``, which is a sort - of `scaled` mutual information. The actual mutual information could be - obtained as ``exp(log_norm) * mi``. + the function will return the tuple ``(entropy, log_norm)``, which is a + sort of `scaled` reduced entropy. This is, indeed, the reduced entropy + of a distribution, since the schmidt values are normalized to sum up + to 1. + + The actual reduced entropy, without rescaling, could be obtained as: + + .. math:: + + \exp(\texttt{log_norm})^2 \cdot S(\rho_A) - + \exp(\texttt{log_norm})^2 \cdot 2 \cdot \texttt{log_norm} Parameters ---------- @@ -1653,7 +1809,13 @@

Source code for tensorkrowch.models.mps

             result2 = result2.parameterize()
             nodes[i] = result2
             nodes[i - 1] = result1
-
+        
+        if renormalize:
+            aux_norm = nodes[middle_site].norm()
+            if not aux_norm.isinf() and (aux_norm > 0):
+                nodes[middle_site].tensor = nodes[middle_site].tensor / aux_norm
+                log_norm += aux_norm.log()
+        
         nodes[middle_site] = nodes[middle_site].parameterize()
         
         # Compute mutual information
@@ -1664,7 +1826,7 @@ 

Source code for tensorkrowch.models.mps

             full_matrices=False)
         
         s = s[s > 0]
-        mutual_info = -(s * (s.log() + log_norm)).sum()
+        entropy = -(s.pow(2) * s.pow(2).log()).sum()
         
         # Rescale
         if log_norm != 0:
@@ -1684,9 +1846,9 @@ 

Source code for tensorkrowch.models.mps

         self.auto_stack = prev_auto_stack
         
         if renormalize:
-            return mutual_info, log_norm
+            return entropy, log_norm
         else:
-            return mutual_info
+ return entropy
[docs] @torch.no_grad() def canonicalize(self, @@ -2174,17 +2336,39 @@

Source code for tensorkrowch.models.mps

         for node in self._mats_env:
             node.set_tensor_from(uniform_memory)
     
-    def _make_unitaries(self, device: Optional[torch.device] = None) -> torch.Tensor:
-        """Initializes MPS in canonical form."""
+    def _make_canonical(self, device: Optional[torch.device] = None) -> List[torch.Tensor]:
+        """
+        Creates random unitaries to initialize the MPS in canonical form with
+        orthogonality center at the rightmost node. Unitaries in nodes are
+        scaled so that the total norm squared of the initial MPS is the product
+        of all the physical dimensions.
+        """
         node = self.uniform_memory
         node_shape = node.shape
         aux_shape = (node.shape[:2].numel(), node.shape[2])
         
         size = max(aux_shape[0], aux_shape[1])
+        phys_dim = node_shape[1]
         
         tensor = random_unitary(size, device=device)
         tensor = tensor[:min(aux_shape[0], size), :min(aux_shape[1], size)]
         tensor = tensor.reshape(*node_shape)
+        tensor = tensor * sqrt(phys_dim)
+        return tensor
+    
+    def _make_unitaries(self, device: Optional[torch.device] = None) -> List[torch.Tensor]:
+        """
+        Creates random unitaries to initialize the MPS nodes as stacks of
+        unitaries.
+        """
+        node = self.uniform_memory
+        node_shape = node.shape
+        
+        units = []
+        for _ in range(node_shape[1]):
+            tensor = random_unitary(node_shape[0], device=device)
+            units.append(tensor)
+        tensor = torch.stack(units, dim=1)
         return tensor
 
 
[docs] def initialize(self, @@ -2207,8 +2391,15 @@

Source code for tensorkrowch.models.mps

           top of a random gaussian tensor. In this case, ``std`` should be
           specified with a low value, e.g., ``std = 1e-9``.
         
-        * ``"unit"``: Tensor is initialized as a random unitary, so that the
-          MPS is in canonical form.
+        * ``"unit"``: Tensor is initialized as a stack of random unitaries. This,
+          combined (at least) with an embedding of the inputs as elements of
+          the computational basis (:func:`~tensorkrowch.embeddings.discretize`
+          combined with :func:`~tensorkrowch.embeddings.basis`)
+        
+        * ``"canonical"```: MPS is initialized in canonical form with a squared
+          norm `close` to the product of all the physical dimensions (if bond
+          dimensions are bigger than the powers of the physical dimensions,
+          the norm could vary).
         
         Parameters
         ----------
@@ -2216,7 +2407,7 @@ 

Source code for tensorkrowch.models.mps

             Sequence of a single tensor to set in each of the MPS nodes. The
             tensor should be rank-3, with its first and last dimensions being
             equal.
-        init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit"}, optional
+        init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit", "canonical"}, optional
             Initialization method.
         device : torch.device, optional
             Device where to initialize the tensors if ``init_method`` is provided.
@@ -2228,6 +2419,8 @@ 

Source code for tensorkrowch.models.mps

         
         if init_method == 'unit':
             tensors = [self._make_unitaries(device=device)]
+        elif init_method == 'canonical':
+            tensors = [self._make_canonical(device=device)]
         
         if tensors is not None:
             node.tensor = tensors[0]
@@ -2549,6 +2742,208 @@ 

Source code for tensorkrowch.models.mps

         """Returns the output node."""
         return self._mats_env[self._out_position]
     
+    def _make_canonical(self, device: Optional[torch.device] = None) -> List[torch.Tensor]:
+        """
+        Creates random unitaries to initialize the MPS in canonical form with
+        orthogonality center at the rightmost node. Unitaries in nodes are
+        scaled so that the total norm squared of the initial MPS is the product
+        of all the physical dimensions.
+        """
+        # Left nodes
+        left_tensors = []
+        for i, node in enumerate(self._mats_env[:self._out_position]):
+            if self._boundary == 'obc':
+                if i == 0:
+                    node_shape = node.shape[1:]
+                    aux_shape = node_shape
+                    phys_dim = node_shape[0]
+                else:
+                    node_shape = node.shape
+                    aux_shape = (node.shape[:2].numel(), node.shape[2])
+                    phys_dim = node_shape[1]
+            else:
+                node_shape = node.shape
+                aux_shape = (node.shape[:2].numel(), node.shape[2])
+                phys_dim = node_shape[1]
+            size = max(aux_shape[0], aux_shape[1])
+            
+            tensor = random_unitary(size, device=device)
+            tensor = tensor[:min(aux_shape[0], size), :min(aux_shape[1], size)]
+            tensor = tensor.reshape(*node_shape)
+            
+            left_tensors.append(tensor * sqrt(phys_dim))
+        
+        # Output node
+        out_tensor = torch.randn(self.out_node.shape, device=device)
+        phys_dim = out_tensor.shape[1]
+        if self._boundary == 'obc':
+            if self._out_position == 0:
+                out_tensor = out_tensor[0]
+            if self._out_position == (self._n_features - 1):
+                out_tensor = out_tensor[..., 0]
+        out_tensor = out_tensor / out_tensor.norm() * sqrt(phys_dim)
+        
+        # Right nodes
+        right_tensors = []
+        for i, node in enumerate(self._mats_env[-1:self._out_position:-1]):
+            if self._boundary == 'obc':
+                if i == 0:
+                    node_shape = node.shape[:2]
+                    aux_shape = node_shape
+                    phys_dim = node_shape[1]
+                else:
+                    node_shape = node.shape
+                    aux_shape = (node.shape[0], node.shape[1:].numel())
+                    phys_dim = node_shape[1]
+            else:
+                node_shape = node.shape
+                aux_shape = (node.shape[0], node.shape[1:].numel())
+                phys_dim = node_shape[1]
+            size = max(aux_shape[0], aux_shape[1])
+            
+            tensor = random_unitary(size, device=device)
+            tensor = tensor[:min(aux_shape[0], size), :min(aux_shape[1], size)]
+            tensor = tensor.reshape(*node_shape)
+            
+            right_tensors.append(tensor * sqrt(phys_dim))
+        right_tensors.reverse()
+        
+        # All tensors
+        tensors = left_tensors + [out_tensor] + right_tensors
+        return tensors
+    
+    def _make_unitaries(self, device: Optional[torch.device] = None) -> List[torch.Tensor]:
+        """
+        Creates random unitaries to initialize the MPS nodes as stacks of
+        unitaries.
+        """
+        # Left_nodes
+        left_tensors = []
+        for i, node in enumerate(self._mats_env[:self._out_position]):
+            units = []
+            size = max(node.shape[0], node.shape[2])
+            if self._boundary == 'obc':
+                if i == 0:
+                    size_1 = 1
+                    size_2 = min(node.shape[2], size)
+                else:
+                    size_1 = min(node.shape[0], size)
+                    size_2 = min(node.shape[2], size)
+            else:
+                size_1 = min(node.shape[0], size)
+                size_2 = min(node.shape[2], size)
+            
+            for _ in range(node.shape[1]):
+                tensor = random_unitary(size, device=device)
+                tensor = tensor[:size_1, :size_2]
+                units.append(tensor)
+            
+            units = torch.stack(units, dim=1)
+            
+            if self._boundary == 'obc':
+                if i == 0:
+                    left_tensors.append(units.squeeze(0))
+                else:
+                    left_tensors.append(units)
+            else:    
+                left_tensors.append(units)
+        
+        # Output node
+        out_tensor = torch.randn(self.out_node.shape, device=device)
+        if self._boundary == 'obc':
+            if self._out_position == 0:
+                out_tensor = out_tensor[0]
+            if self._out_position == (self._n_features - 1):
+                out_tensor = out_tensor[..., 0]
+        
+        # Right nodes
+        right_tensors = []
+        for i, node in enumerate(self._mats_env[-1:self._out_position:-1]):
+            units = []
+            size = max(node.shape[0], node.shape[2])
+            if self._boundary == 'obc':
+                if i == 0:
+                    size_1 = min(node.shape[0], size)
+                    size_2 = 1
+                else:
+                    size_1 = min(node.shape[0], size)
+                    size_2 = min(node.shape[2], size)
+            else:
+                size_1 = min(node.shape[0], size)
+                size_2 = min(node.shape[2], size)
+            
+            for _ in range(node.shape[1]):
+                tensor = random_unitary(size, device=device).t()
+                tensor = tensor[:size_1, :size_2]
+                units.append(tensor)
+            
+            units = torch.stack(units, dim=1)
+            
+            if self._boundary == 'obc':
+                if i == 0:
+                    right_tensors.append(units.squeeze(-1))
+                else:
+                    right_tensors.append(units)
+            else:    
+                right_tensors.append(units)
+        right_tensors.reverse()
+        
+        # All tensors
+        tensors = left_tensors + [out_tensor] + right_tensors
+        return tensors
+
+    def initialize(self,
+                   tensors: Optional[Sequence[torch.Tensor]] = None,
+                   init_method: Optional[Text] = 'randn',
+                   device: Optional[torch.device] = None,
+                   **kwargs: float) -> None:
+        """
+        Initializes all the nodes of the :class:`MPS`. It can be called when
+        instantiating the model, or to override the existing nodes' tensors.
+        
+        There are different methods to initialize the nodes:
+        
+        * ``{"zeros", "ones", "copy", "rand", "randn"}``: Each node is
+          initialized calling :meth:`~tensorkrowch.AbstractNode.set_tensor` with
+          the given method, ``device`` and ``kwargs``.
+        
+        * ``"randn_eye"``: Nodes are initialized as in this
+          `paper <https://arxiv.org/abs/1605.03795>`_, adding identities at the
+          top of random gaussian tensors. In this case, ``std`` should be
+          specified with a low value, e.g., ``std = 1e-9``.
+        
+        * ``"unit"``: Nodes are initialized as stacks of random unitaries. This,
+          combined (at least) with an embedding of the inputs as elements of
+          the computational basis (:func:`~tensorkrowch.embeddings.discretize`
+          combined with :func:`~tensorkrowch.embeddings.basis`)
+        
+        * ``"canonical"```: MPS is initialized in canonical form with a squared
+          norm `close` to the product of all the physical dimensions (if bond
+          dimensions are bigger than the powers of the physical dimensions,
+          the norm could vary). Th orthogonality center is at the rightmost
+          node.
+        
+        Parameters
+        ----------
+        tensors : list[torch.Tensor] or tuple[torch.Tensor], optional
+            Sequence of tensors to set in each of the MPS nodes. If ``boundary``
+            is ``"obc"``, all tensors should be rank-3, except the first and
+            last ones, which can be rank-2, or rank-1 (if the first and last are
+            the same). If ``boundary`` is ``"pbc"``, all tensors should be
+            rank-3.
+        init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit", "canonical"}, optional
+            Initialization method.
+        device : torch.device, optional
+            Device where to initialize the tensors if ``init_method`` is provided.
+        kwargs : float
+            Keyword arguments for the different initialization methods. See
+            :meth:`~tensorkrowch.AbstractNode.make_tensor`.
+        """
+        if init_method == 'unit':
+            tensors = self._make_unitaries(device=device)
+        elif init_method == 'canonical':
+            tensors = self._make_canonical(device=device)
+    
 
[docs] def initialize(self, tensors: Optional[Sequence[torch.Tensor]] = None, init_method: Optional[Text] = 'randn', @@ -2569,9 +2964,15 @@

Source code for tensorkrowch.models.mps

           top of random gaussian tensors. In this case, ``std`` should be
           specified with a low value, e.g., ``std = 1e-9``.
         
-        * ``"unit"``: Nodes are initialized as random unitaries, so that the
-          MPS is in canonical form, with the orthogonality center at the
-          rightmost node.
+        * ``"unit"``: Nodes are initialized as stacks of random unitaries. This,
+          combined (at least) with an embedding of the inputs as elements of
+          the computational basis (:func:`~tensorkrowch.embeddings.discretize`
+          combined with :func:`~tensorkrowch.embeddings.basis`)
+        
+        * ``"canonical"```: MPS is initialized in canonical form with a squared
+          norm `close` to the product of all the physical dimensions (if bond
+          dimensions are bigger than the powers of the physical dimensions,
+          the norm could vary). Th orthogonality center is at the output node.
         
         Parameters
         ----------
@@ -2581,7 +2982,7 @@ 

Source code for tensorkrowch.models.mps

             last ones, which can be rank-2, or rank-1 (if the first and last are
             the same). If ``boundary`` is ``"pbc"``, all tensors should be
             rank-3.
-        init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit"}, optional
+        init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit", "canonical"}, optional
             Initialization method.
         device : torch.device, optional
             Device where to initialize the tensors if ``init_method`` is provided.
@@ -2589,12 +2990,10 @@ 

Source code for tensorkrowch.models.mps

             Keyword arguments for the different initialization methods. See
             :meth:`~tensorkrowch.AbstractNode.make_tensor`.
         """
-        if self._boundary == 'obc':
-            self._left_node.set_tensor(init_method='copy', device=device)
-            self._right_node.set_tensor(init_method='copy', device=device)
-        
         if init_method == 'unit':
             tensors = self._make_unitaries(device=device)
+        elif init_method == 'canonical':
+            tensors = self._make_canonical(device=device)
 
         if tensors is not None:
             if len(tensors) != self._n_features:
@@ -2603,18 +3002,22 @@ 

Source code for tensorkrowch.models.mps

             
             if self._boundary == 'obc':
                 tensors = tensors[:]
+                
+                if device is None:
+                    device = tensors[0].device
+                
                 if len(tensors) == 1:
                     tensors[0] = tensors[0].reshape(1, -1, 1)
                 else:
                     # Left node
                     aux_tensor = torch.zeros(*self._mats_env[0].shape,
-                                             device=tensors[0].device)
+                                             device=device)
                     aux_tensor[0] = tensors[0]
                     tensors[0] = aux_tensor
                     
                     # Right node
                     aux_tensor = torch.zeros(*self._mats_env[-1].shape,
-                                             device=tensors[-1].device)
+                                             device=device)
                     aux_tensor[..., 0] = tensors[-1]
                     tensors[-1] = aux_tensor
                 
@@ -2653,7 +3056,11 @@ 

Source code for tensorkrowch.models.mps

                     elif i == (self._n_features - 1):
                         # Right node
                         aux_tensor[..., 0] = node.tensor[..., 0]
-                        node.tensor = aux_tensor
+ node.tensor = aux_tensor + + if self._boundary == 'obc': + self._left_node.set_tensor(init_method='copy', device=device) + self._right_node.set_tensor(init_method='copy', device=device)
[docs] def copy(self, share_tensors: bool = False) -> 'MPSLayer': """ @@ -2897,20 +3304,48 @@

Source code for tensorkrowch.models.mps

         for node in in_nodes:
             node.set_tensor_from(uniform_memory)
     
+    def _make_canonical(self, device: Optional[torch.device] = None) -> List[torch.Tensor]:
+        """
+        Creates random unitaries to initialize the MPS in canonical form with
+        orthogonality center at the rightmost node. Unitaries in nodes are
+        scaled so that the total norm squared of the initial MPS is the product
+        of all the physical dimensions.
+        """
+        # Uniform node
+        node = self.uniform_memory
+        node_shape = node.shape
+        aux_shape = (node.shape[:2].numel(), node.shape[2])
+        
+        size = max(aux_shape[0], aux_shape[1])
+        phys_dim = node_shape[1]
+        
+        uni_tensor = random_unitary(size, device=device)
+        uni_tensor = uni_tensor[:min(aux_shape[0], size), :min(aux_shape[1], size)]
+        uni_tensor = uni_tensor.reshape(*node_shape)
+        uni_tensor = uni_tensor * sqrt(phys_dim)
+        
+        # Output node
+        out_tensor = torch.randn(self.out_node.shape, device=device)
+        out_tensor = out_tensor / out_tensor.norm() * sqrt(out_tensor.shape[1])
+        
+        return [uni_tensor, out_tensor]
+    
     def _make_unitaries(self, device: Optional[torch.device] = None) -> List[torch.Tensor]:
-        """Initializes MPS in canonical form."""
+        """
+        Creates random unitaries to initialize the MPS nodes as stacks of
+        unitaries.
+        """
         tensors = []
         for node in [self.uniform_memory, self.out_node]:
             node_shape = node.shape
-            aux_shape = (node.shape[:2].numel(), node.shape[2])
             
-            size = max(aux_shape[0], aux_shape[1])
+            units = []
+            for _ in range(node_shape[1]):
+                tensor = random_unitary(node_shape[0], device=device)
+                units.append(tensor)
             
-            tensor = random_unitary(size, device=device)
-            tensor = tensor[:min(aux_shape[0], size), :min(aux_shape[1], size)]
-            tensor = tensor.reshape(*node_shape)
-            
-            tensors.append(tensor)
+            tensors.append(torch.stack(units, dim=1))
+        
         return tensors
 
 
[docs] def initialize(self, @@ -2932,9 +3367,16 @@

Source code for tensorkrowch.models.mps

           `paper <https://arxiv.org/abs/1605.03795>`_, adding identities at the
           top of a random gaussian tensor. In this case, ``std`` should be
           specified with a low value, e.g., ``std = 1e-9``.
+          
+        * ``"unit"``: Tensor is initialized as a stack of random unitaries. This,
+          combined (at least) with an embedding of the inputs as elements of
+          the computational basis (:func:`~tensorkrowch.embeddings.discretize`
+          combined with :func:`~tensorkrowch.embeddings.basis`)
         
-        * ``"unit"``: Tensor is initialized as a random unitary, so that the
-          MPS is in canonical form.
+        * ``"canonical"```: MPS is initialized in canonical form with a squared
+          norm `close` to the product of all the physical dimensions (if bond
+          dimensions are bigger than the powers of the physical dimensions,
+          the norm could vary).
         
         Parameters
         ----------
@@ -2943,7 +3385,7 @@ 

Source code for tensorkrowch.models.mps

             that will be set in all input nodes, and the second one will be the
             output node's tensor. Both tensors should be rank-3, with all their
             first and last dimensions being equal.
-        init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit"}, optional
+        init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit", "canonical"}, optional
             Initialization method.
         device : torch.device, optional
             Device where to initialize the tensors if ``init_method`` is provided.
@@ -2953,6 +3395,8 @@ 

Source code for tensorkrowch.models.mps

         """
         if init_method == 'unit':
             tensors = self._make_unitaries(device=device)
+        elif init_method == 'canonical':
+            tensors = self._make_canonical(device=device)
         
         if tensors is not None:
             self.uniform_memory.tensor = tensors[0]
diff --git a/docs/_build/html/_modules/tensorkrowch/models/mps_data.html b/docs/_build/html/_modules/tensorkrowch/models/mps_data.html
index c3727e0..e1ca363 100644
--- a/docs/_build/html/_modules/tensorkrowch/models/mps_data.html
+++ b/docs/_build/html/_modules/tensorkrowch/models/mps_data.html
@@ -5,7 +5,7 @@
   
     
     
-    tensorkrowch.models.mps_data — TensorKrowch 1.0.1 documentation
+    tensorkrowch.models.mps_data — TensorKrowch 1.1.0 documentation
     
   
   
@@ -662,7 +662,6 @@ 

Source code for tensorkrowch.models.mps_data

self._right_node.set_tensor(init_method='copy', device=device)
                 
         if init_method is not None:
-                
             for i, node in enumerate(self._mats_env):
                 node.set_tensor(init_method=init_method,
                                 device=device,
@@ -724,31 +723,44 @@ 

Source code for tensorkrowch.models.mps_data

f'{node.shape[-2]} of the MPS')
         
         data = data[:]
+        device = data[0].device
         for i, node in enumerate(self._mats_env):
             if self._boundary == 'obc':
-                aux_tensor = torch.zeros(*node.shape,
-                                         device=data[i].device)
+                aux_tensor = torch.zeros(*node.shape, device=device)
                 if (i == 0) and (i == (self._n_features - 1)):
                     aux_tensor = torch.zeros(*data[i].shape[:-1],
                                              *node.shape[-3:],
-                                             device=data[i].device)
+                                             device=device)
                     aux_tensor[..., 0, :, 0] = data[i]
                     data[i] = aux_tensor
                 elif i == 0:
                     aux_tensor = torch.zeros(*data[i].shape[:-2],
                                              *node.shape[-3:-1],
                                              data[i].shape[-1],
-                                             device=data[i].device)
+                                             device=device)
                     aux_tensor[..., 0, :, :] = data[i]
                     data[i] = aux_tensor
                 elif i == (self._n_features - 1):
                     aux_tensor = torch.zeros(*data[i].shape[:-1],
                                              *node.shape[-2:],
-                                             device=data[i].device)
+                                             device=device)
                     aux_tensor[..., 0] = data[i]
                     data[i] = aux_tensor
                     
-            node._direct_set_tensor(data[i])
+ node._direct_set_tensor(data[i]) + + # Send left and right nodes to correct device + if self._boundary == 'obc': + if self._left_node.device != device: + self._left_node.tensor = self._left_node.tensor.to(device) + if self._right_node.device != device: + self._right_node.tensor = self._right_node.tensor.to(device) + + # Update bond dim + if self._boundary == 'obc': + self._bond_dim = [node['right'].size() for node in self._mats_env[:-1]] + else: + self._bond_dim = [node['right'].size() for node in self._mats_env]
diff --git a/docs/_build/html/_modules/tensorkrowch/models/peps.html b/docs/_build/html/_modules/tensorkrowch/models/peps.html index 48ace85..16c37f4 100644 --- a/docs/_build/html/_modules/tensorkrowch/models/peps.html +++ b/docs/_build/html/_modules/tensorkrowch/models/peps.html @@ -5,7 +5,7 @@ - tensorkrowch.models.peps — TensorKrowch 1.0.1 documentation + tensorkrowch.models.peps — TensorKrowch 1.1.0 documentation diff --git a/docs/_build/html/_modules/tensorkrowch/models/tree.html b/docs/_build/html/_modules/tensorkrowch/models/tree.html index dca9f9a..f285eb0 100644 --- a/docs/_build/html/_modules/tensorkrowch/models/tree.html +++ b/docs/_build/html/_modules/tensorkrowch/models/tree.html @@ -5,7 +5,7 @@ - tensorkrowch.models.tree — TensorKrowch 1.0.1 documentation + tensorkrowch.models.tree — TensorKrowch 1.1.0 documentation diff --git a/docs/_build/html/_modules/tensorkrowch/operations.html b/docs/_build/html/_modules/tensorkrowch/operations.html index 3071e07..11c9a88 100644 --- a/docs/_build/html/_modules/tensorkrowch/operations.html +++ b/docs/_build/html/_modules/tensorkrowch/operations.html @@ -5,7 +5,7 @@ - tensorkrowch.operations — TensorKrowch 1.0.1 documentation + tensorkrowch.operations — TensorKrowch 1.1.0 documentation @@ -326,6 +326,7 @@

Source code for tensorkrowch.operations

 
 import types
 from typing import Callable
+from numbers import Number
 
 from itertools import starmap
 import opt_einsum
@@ -689,7 +690,7 @@ 

Source code for tensorkrowch.operations

                 node2: AbstractNode) -> Node:
     tensor1 = node1._direct_get_tensor(successor.node_ref[0],
                                        successor.index[0])
-    tensor2 = node1._direct_get_tensor(successor.node_ref[1],
+    tensor2 = node2._direct_get_tensor(successor.node_ref[1],
                                        successor.index[1])
     new_tensor = torch.outer(tensor1.flatten(),
                              tensor2.flatten()).view(*(list(node1._shape) + 
@@ -777,19 +778,34 @@ 

Source code for tensorkrowch.operations

 ###################################   MUL    ##################################
 # MARK: mul
 def _check_first_mul(node1: AbstractNode,
-                     node2: AbstractNode) -> Optional[Successor]:
-    args = (node1, node2)
+                     node2: Union[AbstractNode,
+                                  torch.Tensor,
+                                  Number]) -> Optional[Successor]:
+    if isinstance(node2, AbstractNode):
+        args = (node1, node2)
+    else:
+        args = (node1,)
     successors = node1._successors.get('mul')
     if not successors:
         return None
     return successors.get(args)
 
 
-def _mul_first(node1: AbstractNode, node2: AbstractNode) -> Node:
-    if node1._network != node2._network:
-        raise ValueError('Nodes must be in the same network')
+def _mul_first(node1: AbstractNode,
+               node2: Union[AbstractNode,
+                            torch.Tensor,
+                            Number]) -> Node:
+    is_node2 = False
+    if isinstance(node2, AbstractNode):
+        is_node2 = True
+        if node1._network != node2._network:
+            raise ValueError('Nodes must be in the same network')
 
-    new_tensor = node1.tensor * node2.tensor
+    if is_node2:
+        new_tensor = node1.tensor * node2.tensor
+    else:
+        new_tensor = node1.tensor * node2
+    
     new_node = Node._create_resultant(axes_names=node1.axes_names,
                                       name='mul',
                                       network=node1._network,
@@ -799,12 +815,21 @@ 

Source code for tensorkrowch.operations

 
     # Create successor
     net = node1._network
-    args = (node1, node2)
-    successor = Successor(node_ref=(node1.node_ref(),
-                                    node2.node_ref()),
-                          index=(node1._tensor_info['index'],
-                                 node2._tensor_info['index']),
-                          child=new_node)
+    
+    if is_node2:
+        args = (node1, node2)
+        successor = Successor(node_ref=(node1.node_ref(),
+                                        node2.node_ref()),
+                              index=(node1._tensor_info['index'],
+                                     node2._tensor_info['index']),
+                              child=new_node,
+                              hints=is_node2)
+    else:
+        args = (node1,)
+        successor = Successor(node_ref=(node1.node_ref(),),
+                              index=(node1._tensor_info['index'],),
+                              child=new_node,
+                              hints=is_node2)
 
     # Add successor to parent
     if 'mul' in node1._successors:
@@ -818,18 +843,27 @@ 

Source code for tensorkrowch.operations

     # Record in inverse_memory while tracing
     if net._tracing:
         node1._record_in_inverse_memory()
-        node2._record_in_inverse_memory()
+        
+        if is_node2:
+            node2._record_in_inverse_memory()
 
     return new_node
 
 
 def _mul_next(successor: Successor,
               node1: AbstractNode,
-              node2: AbstractNode) -> Node:
+              node2: Union[AbstractNode,
+                           torch.Tensor,
+                           Number]) -> Node:
+    is_node2 = successor.hints
     tensor1 = node1._direct_get_tensor(successor.node_ref[0],
                                        successor.index[0])
-    tensor2 = node1._direct_get_tensor(successor.node_ref[1],
-                                       successor.index[1])
+    if is_node2:
+        tensor2 = node2._direct_get_tensor(successor.node_ref[1],
+                                           successor.index[1])
+    else:
+        tensor2 = node2
+    
     new_tensor = tensor1 * tensor2
     child = successor.child
     child._direct_set_tensor(new_tensor)
@@ -837,20 +871,32 @@ 

Source code for tensorkrowch.operations

     # Record in inverse_memory while contracting, if network is traced
     # (to delete memory if possible)
     if node1._network._traced:
-        node1._check_inverse_memory(successor.node_ref)
-        node2._check_inverse_memory(successor.node_ref)
-
+        node1._check_inverse_memory(successor.node_ref[0])
+        
+        if is_node2:
+            node2._check_inverse_memory(successor.node_ref[1])
+    
     return child
 
 
 mul_op = Operation('mul', _check_first_mul, _mul_first, _mul_next)
 
 
-
[docs]def mul(node1: AbstractNode, node2: AbstractNode) -> Node: +
[docs]def mul(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: """ Element-wise product between two nodes. It can also be performed using the operator ``*``. + It also admits to take as ``node2`` a number or tensor, that will be + multiplied by the ``node1`` tensor as ``node1.tensor * node2``. If this + is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method + of a :class:`~tensorkrowch.TensorNetwork`, this will have to be called + explicitly to contract the network, rather than relying on its internal + call via the :meth:`~tensorkrowch.TensorNetwork.forward`. + Nodes ``resultant`` from this operation are called ``"mul"``. The node that keeps information about the :class:`Successor` is ``node1``. @@ -858,8 +904,9 @@

Source code for tensorkrowch.operations

     ----------
     node1 : Node or ParamNode
         First node to be multiplied. Its edges will appear in the resultant node.
-    node2 : Node or ParamNode
-        Second node to be multiplied.
+    node2 : Node, ParamNode, torch.Tensor or number
+        Second node to be multiplied. It can also be a number or tensor with
+        appropiate shape.
 
     Returns
     -------
@@ -873,6 +920,13 @@ 

Source code for tensorkrowch.operations

     >>> result = nodeA * nodeB
     >>> result.shape
     torch.Size([2, 3])
+    
+    >>> net = tk.TensorNetwork()
+    >>> nodeA = tk.randn((2, 3), network=net)
+    >>> tensorB = torch.randn(2, 3)
+    >>> result = nodeA * tensorB
+    >>> result.shape
+    torch.Size([2, 3])
     """
     return mul_op(node1, node2)
@@ -883,13 +937,21 @@

Source code for tensorkrowch.operations

     Element-wise product between two nodes. It can also be performed using the
     operator ``*``.
     
+    It also admits to take as ``node2`` a number or tensor, that will be
+    multiplied by the ``self`` tensor as ``self.tensor * node2``. If this
+    is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method
+    of a  :class:`~tensorkrowch.TensorNetwork`, this will have to be called
+    explicitly to contract the network, rather than relying on its internal
+    call via the :meth:`~tensorkrowch.TensorNetwork.forward`.
+    
     Nodes ``resultant`` from this operation are called ``"mul"``. The node
     that keeps information about the :class:`Successor` is ``self``.
 
     Parameters
     ----------
-    node2 : Node or ParamNode
-        Second node to be multiplied.
+    node2 : Node, ParamNode, torch.Tensor or number
+        Second node to be multiplied. It can also be a number or tensor with
+        appropiate shape.
 
     Returns
     -------
@@ -903,27 +965,251 @@ 

Source code for tensorkrowch.operations

     >>> result = nodeA.mul(nodeB)
     >>> result.shape
     torch.Size([2, 3])
+    
+    >>> net = tk.TensorNetwork()
+    >>> nodeA = tk.randn((2, 3), network=net)
+    >>> tensorB = torch.randn(2, 3)
+    >>> result = nodeA.mul(tensorB)
+    >>> result.shape
+    torch.Size([2, 3])
     """
 
 AbstractNode.__mul__ = mul_node
 
 
+###################################   DIV    ##################################
+# MARK: div
+def _check_first_div(node1: AbstractNode,
+                     node2: Union[AbstractNode,
+                                  torch.Tensor,
+                                  Number]) -> Optional[Successor]:
+    if isinstance(node2, AbstractNode):
+        args = (node1, node2)
+    else:
+        args = (node1,)
+    successors = node1._successors.get('div')
+    if not successors:
+        return None
+    return successors.get(args)
+
+
+def _div_first(node1: AbstractNode,
+               node2: Union[AbstractNode,
+                            torch.Tensor,
+                            Number]) -> Node:
+    is_node2 = False
+    if isinstance(node2, AbstractNode):
+        is_node2 = True
+        if node1._network != node2._network:
+            raise ValueError('Nodes must be in the same network')
+
+    if is_node2:
+        new_tensor = node1.tensor / node2.tensor
+    else:
+        new_tensor = node1.tensor / node2
+    
+    new_node = Node._create_resultant(axes_names=node1.axes_names,
+                                      name='div',
+                                      network=node1._network,
+                                      tensor=new_tensor,
+                                      edges=node1._edges,
+                                      node1_list=node1.is_node1())
+
+    # Create successor
+    net = node1._network
+    
+    if is_node2:
+        args = (node1, node2)
+        successor = Successor(node_ref=(node1.node_ref(),
+                                        node2.node_ref()),
+                              index=(node1._tensor_info['index'],
+                                     node2._tensor_info['index']),
+                              child=new_node,
+                              hints=is_node2)
+    else:
+        args = (node1,)
+        successor = Successor(node_ref=(node1.node_ref(),),
+                              index=(node1._tensor_info['index'],),
+                              child=new_node,
+                              hints=is_node2)
+
+    # Add successor to parent
+    if 'div' in node1._successors:
+        node1._successors['div'].update({args: successor})
+    else:
+        node1._successors['div'] = {args: successor}
+
+    # Add operation to list of performed operations of TN
+    net._seq_ops.append(('div', args))
+
+    # Record in inverse_memory while tracing
+    if net._tracing:
+        node1._record_in_inverse_memory()
+        
+        if is_node2:
+            node2._record_in_inverse_memory()
+
+    return new_node
+
+
+def _div_next(successor: Successor,
+              node1: AbstractNode,
+              node2: Union[AbstractNode,
+                           torch.Tensor,
+                           Number]) -> Node:
+    is_node2 = successor.hints
+    tensor1 = node1._direct_get_tensor(successor.node_ref[0],
+                                       successor.index[0])
+    if is_node2:
+        tensor2 = node2._direct_get_tensor(successor.node_ref[1],
+                                           successor.index[1])
+    else:
+        tensor2 = node2
+    
+    new_tensor = tensor1 / tensor2
+    child = successor.child
+    child._direct_set_tensor(new_tensor)
+
+    # Record in inverse_memory while contracting, if network is traced
+    # (to delete memory if possible)
+    if node1._network._traced:
+        node1._check_inverse_memory(successor.node_ref[0])
+        
+        if is_node2:
+            node2._check_inverse_memory(successor.node_ref[1])
+    
+    return child
+
+
+div_op = Operation('div', _check_first_div, _div_first, _div_next)
+
+
+
[docs]def div(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: + """ + Element-wise division between two nodes. It can also be performed using the + operator ``/``. + + It also admits to take as ``node2`` a number or tensor, that will + divide the ``node1`` tensor as ``node1.tensor / node2``. If this + is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method + of a :class:`~tensorkrowch.TensorNetwork`, this will have to be called + explicitly to contract the network, rather than relying on its internal + call via the :meth:`~tensorkrowch.TensorNetwork.forward`. + + Nodes ``resultant`` from this operation are called ``"div"``. The node + that keeps information about the :class:`Successor` is ``node1``. + + Parameters + ---------- + node1 : Node or ParamNode + First node to be divided. Its edges will appear in the resultant node. + node2 : Node, ParamNode, torch.Tensor or number + Second node, the divisor. It can also be a number or tensor with + appropiate shape. + + Returns + ------- + Node + + Examples + -------- + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> nodeB = tk.randn((2, 3), network=net) + >>> result = nodeA / nodeB + >>> result.shape + torch.Size([2, 3]) + + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> tensorB = torch.randn(2, 3) + >>> result = nodeA / tensorB + >>> result.shape + torch.Size([2, 3]) + """ + return div_op(node1, node2)
+ + +div_node = copy_func(div) +div_node.__doc__ = \ + """ + Element-wise division between two nodes. It can also be performed using the + operator ``/``. + + It also admits to take as ``node2`` a number or tensor, that will + divide the ``self`` tensor as ``self.tensor / node2``. If this + is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method + of a :class:`~tensorkrowch.TensorNetwork`, this will have to be called + explicitly to contract the network, rather than relying on its internal + call via the :meth:`~tensorkrowch.TensorNetwork.forward`. + + Nodes ``resultant`` from this operation are called ``"div"``. The node + that keeps information about the :class:`Successor` is ``self``. + + Parameters + ---------- + node2 : Node, ParamNode, torch.Tensor or number + Second node, the divisor. It can also be a number or tensor with + appropiate shape. + + Returns + ------- + Node + + Examples + -------- + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> nodeB = tk.randn((2, 3), network=net) + >>> result = nodeA.div(nodeB) + >>> result.shape + torch.Size([2, 3]) + + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> tensorB = torch.randn(2, 3) + >>> result = nodeA.div(tensorB) + >>> result.shape + torch.Size([2, 3]) + """ + +AbstractNode.__truediv__ = div_node + + ################################### ADD ################################## # MARK: add def _check_first_add(node1: AbstractNode, - node2: AbstractNode) -> Optional[Successor]: - args = (node1, node2) + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Optional[Successor]: + if isinstance(node2, AbstractNode): + args = (node1, node2) + else: + args = (node1,) successors = node1._successors.get('add') if not successors: return None return successors.get(args) -def _add_first(node1: AbstractNode, node2: AbstractNode) -> Node: - if node1._network != node2._network: - raise ValueError('Nodes must be in the same network') +def _add_first(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: + is_node2 = False + if isinstance(node2, AbstractNode): + is_node2 = True + if node1._network != node2._network: + raise ValueError('Nodes must be in the same network') - new_tensor = node1.tensor + node2.tensor + if is_node2: + new_tensor = node1.tensor + node2.tensor + else: + new_tensor = node1.tensor + node2 + new_node = Node._create_resultant(axes_names=node1.axes_names, name='add', network=node1._network, @@ -933,12 +1219,21 @@

Source code for tensorkrowch.operations

 
     # Create successor
     net = node1._network
-    args = (node1, node2)
-    successor = Successor(node_ref=(node1.node_ref(),
-                                    node2.node_ref()),
-                          index=(node1._tensor_info['index'],
-                                 node2._tensor_info['index']),
-                          child=new_node)
+    
+    if is_node2:
+        args = (node1, node2)
+        successor = Successor(node_ref=(node1.node_ref(),
+                                        node2.node_ref()),
+                              index=(node1._tensor_info['index'],
+                                     node2._tensor_info['index']),
+                              child=new_node,
+                              hints=is_node2)
+    else:
+        args = (node1,)
+        successor = Successor(node_ref=(node1.node_ref(),),
+                              index=(node1._tensor_info['index'],),
+                              child=new_node,
+                              hints=is_node2)
 
     # Add successor to parent
     if 'add' in node1._successors:
@@ -952,18 +1247,27 @@ 

Source code for tensorkrowch.operations

     # Record in inverse_memory while tracing
     if net._tracing:
         node1._record_in_inverse_memory()
-        node2._record_in_inverse_memory()
+        
+        if is_node2:
+            node2._record_in_inverse_memory()
 
     return new_node
 
 
 def _add_next(successor: Successor,
               node1: AbstractNode,
-              node2: AbstractNode) -> Node:
+              node2: Union[AbstractNode,
+                           torch.Tensor,
+                           Number]) -> Node:
+    is_node2 = successor.hints
     tensor1 = node1._direct_get_tensor(successor.node_ref[0],
                                        successor.index[0])
-    tensor2 = node1._direct_get_tensor(successor.node_ref[1],
-                                       successor.index[1])
+    if is_node2:
+        tensor2 = node2._direct_get_tensor(successor.node_ref[1],
+                                           successor.index[1])
+    else:
+        tensor2 = node2
+    
     new_tensor = tensor1 + tensor2
     child = successor.child
     child._direct_set_tensor(new_tensor)
@@ -971,8 +1275,10 @@ 

Source code for tensorkrowch.operations

     # Record in inverse_memory while contracting, if network is traced
     # (to delete memory if possible)
     if node1._network._traced:
-        node1._check_inverse_memory(successor.node_ref)
-        node2._check_inverse_memory(successor.node_ref)
+        node1._check_inverse_memory(successor.node_ref[0])
+        
+        if is_node2:
+            node2._check_inverse_memory(successor.node_ref[1])
 
     return child
 
@@ -980,11 +1286,21 @@ 

Source code for tensorkrowch.operations

 add_op = Operation('add', _check_first_add, _add_first, _add_next)
 
 
-
[docs]def add(node1: AbstractNode, node2: AbstractNode) -> Node: +
[docs]def add(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: """ Element-wise addition between two nodes. It can also be performed using the operator ``+``. + It also admits to take as ``node2`` a number or tensor, that will be + added to the ``node1`` tensor as ``node1.tensor + node2``. If this + is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method + of a :class:`~tensorkrowch.TensorNetwork`, this will have to be called + explicitly to contract the network, rather than relying on its internal + call via the :meth:`~tensorkrowch.TensorNetwork.forward`. + Nodes ``resultant`` from this operation are called ``"add"``. The node that keeps information about the :class:`Successor` is ``node1``. @@ -992,8 +1308,9 @@

Source code for tensorkrowch.operations

     ----------
     node1 : Node or ParamNode
         First node to be added. Its edges will appear in the resultant node.
-    node2 : Node or ParamNode
-        Second node to be added.
+    node2 : Node, ParamNode, torch.Tensor or numeric
+        Second node to be added. It can also be a number or tensor with
+        appropiate shape.
 
     Returns
     -------
@@ -1007,6 +1324,13 @@ 

Source code for tensorkrowch.operations

     >>> result = nodeA + nodeB
     >>> result.shape
     torch.Size([2, 3])
+    
+    >>> net = tk.TensorNetwork()
+    >>> nodeA = tk.randn((2, 3), network=net)
+    >>> tensorB = torch.randn(2, 3)
+    >>> result = nodeA + tensorB
+    >>> result.shape
+    torch.Size([2, 3])
     """
     return add_op(node1, node2)
@@ -1017,13 +1341,21 @@

Source code for tensorkrowch.operations

     Element-wise addition between two nodes. It can also be performed using the
     operator ``+``.
     
+    It also admits to take as ``node2`` a number or tensor, that will be
+    added to the ``self`` tensor as ``self.tensor + node2``. If this
+    is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method
+    of a  :class:`~tensorkrowch.TensorNetwork`, this will have to be called
+    explicitly to contract the network, rather than relying on its internal
+    call via the :meth:`~tensorkrowch.TensorNetwork.forward`.
+    
     Nodes ``resultant`` from this operation are called ``"add"``. The node
     that keeps information about the :class:`Successor` is ``self``.
 
     Parameters
     ----------
-    node2 : Node or ParamNode
-        Second node to be multiplied.
+    node2 : Node, ParamNode, torch.Tensor or number
+        Second node to be added. It can also be a number or tensor with
+        appropiate shape.
 
     Returns
     -------
@@ -1037,6 +1369,13 @@ 

Source code for tensorkrowch.operations

     >>> result = nodeA.add(nodeB)
     >>> result.shape
     torch.Size([2, 3])
+    
+    >>> net = tk.TensorNetwork()
+    >>> nodeA = tk.randn((2, 3), network=net)
+    >>> tensorB = torch.randn(2, 3)
+    >>> result = nodeA.add(tensorB)
+    >>> result.shape
+    torch.Size([2, 3])
     """
 
 AbstractNode.__add__ = add_node
@@ -1045,19 +1384,34 @@ 

Source code for tensorkrowch.operations

 ###################################   SUB    ##################################
 # MARK: sub
 def _check_first_sub(node1: AbstractNode,
-                     node2: AbstractNode) -> Optional[Successor]:
-    args = (node1, node2)
+                     node2: Union[AbstractNode,
+                                  torch.Tensor,
+                                  Number]) -> Optional[Successor]:
+    if isinstance(node2, AbstractNode):
+        args = (node1, node2)
+    else:
+        args = (node1,)
     successors = node1._successors.get('sub')
     if not successors:
         return None
     return successors.get(args)
 
 
-def _sub_first(node1: AbstractNode, node2: AbstractNode) -> Node:
-    if node1._network != node2._network:
-        raise ValueError('Nodes must be in the same network')
+def _sub_first(node1: AbstractNode,
+               node2: Union[AbstractNode,
+                            torch.Tensor,
+                            Number]) -> Node:
+    is_node2 = False
+    if isinstance(node2, AbstractNode):
+        is_node2 = True
+        if node1._network != node2._network:
+            raise ValueError('Nodes must be in the same network')
 
-    new_tensor = node1.tensor - node2.tensor
+    if is_node2:
+        new_tensor = node1.tensor - node2.tensor
+    else:
+        new_tensor = node1.tensor - node2
+    
     new_node = Node._create_resultant(axes_names=node1.axes_names,
                                       name='sub',
                                       network=node1._network,
@@ -1067,12 +1421,21 @@ 

Source code for tensorkrowch.operations

 
     # Create successor
     net = node1._network
-    args = (node1, node2)
-    successor = Successor(node_ref=(node1.node_ref(),
-                                    node2.node_ref()),
-                          index=(node1._tensor_info['index'],
-                                 node2._tensor_info['index']),
-                          child=new_node)
+    
+    if is_node2:
+        args = (node1, node2)
+        successor = Successor(node_ref=(node1.node_ref(),
+                                        node2.node_ref()),
+                              index=(node1._tensor_info['index'],
+                                     node2._tensor_info['index']),
+                              child=new_node,
+                              hints=is_node2)
+    else:
+        args = (node1,)
+        successor = Successor(node_ref=(node1.node_ref(),),
+                              index=(node1._tensor_info['index'],),
+                              child=new_node,
+                              hints=is_node2)
 
     # Add successor to parent
     if 'sub' in node1._successors:
@@ -1086,18 +1449,27 @@ 

Source code for tensorkrowch.operations

     # Record in inverse_memory while tracing
     if net._tracing:
         node1._record_in_inverse_memory()
-        node2._record_in_inverse_memory()
+        
+        if is_node2:
+            node2._record_in_inverse_memory()
 
     return new_node
 
 
 def _sub_next(successor: Successor,
               node1: AbstractNode,
-              node2: AbstractNode) -> Node:
+              node2: Union[AbstractNode,
+                           torch.Tensor,
+                           Number]) -> Node:
+    is_node2 = successor.hints
     tensor1 = node1._direct_get_tensor(successor.node_ref[0],
                                        successor.index[0])
-    tensor2 = node1._direct_get_tensor(successor.node_ref[1],
-                                       successor.index[1])
+    if is_node2:
+        tensor2 = node2._direct_get_tensor(successor.node_ref[1],
+                                           successor.index[1])
+    else:
+        tensor2 = node2
+    
     new_tensor = tensor1 - tensor2
     child = successor.child
     child._direct_set_tensor(new_tensor)
@@ -1105,8 +1477,10 @@ 

Source code for tensorkrowch.operations

     # Record in inverse_memory while contracting, if network is traced
     # (to delete memory if possible)
     if node1._network._traced:
-        node1._check_inverse_memory(successor.node_ref)
-        node2._check_inverse_memory(successor.node_ref)
+        node1._check_inverse_memory(successor.node_ref[0])
+        
+        if is_node2:
+            node2._check_inverse_memory(successor.node_ref[1])
 
     return child
 
@@ -1114,11 +1488,21 @@ 

Source code for tensorkrowch.operations

 sub_op = Operation('sub', _check_first_sub, _sub_first, _sub_next)
 
 
-
[docs]def sub(node1: AbstractNode, node2: AbstractNode) -> Node: +
[docs]def sub(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: """ Element-wise subtraction between two nodes. It can also be performed using the operator ``-``. + It also admits to take as ``node2`` a number or tensor, that will be + subtracted from the ``node1`` tensor as ``node1.tensor - node2``. If this + is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method + of a :class:`~tensorkrowch.TensorNetwork`, this will have to be called + explicitly to contract the network, rather than relying on its internal + call via the :meth:`~tensorkrowch.TensorNetwork.forward`. + Nodes ``resultant`` from this operation are called ``"sub"``. The node that keeps information about the :class:`Successor` is ``node1``. @@ -1126,8 +1510,9 @@

Source code for tensorkrowch.operations

     ----------
     node1 : Node or ParamNode
         First node, minuend . Its edges will appear in the resultant node.
-    node2 : Node or ParamNode
-        Second node, subtrahend.
+    node2 : Node, ParamNode, torch.Tensor or number
+        Second node, subtrahend. It can also be a number or tensor with
+        appropiate shape.
 
     Returns
     -------
@@ -1141,6 +1526,13 @@ 

Source code for tensorkrowch.operations

     >>> result = nodeA - nodeB
     >>> result.shape
     torch.Size([2, 3])
+    
+    >>> net = tk.TensorNetwork()
+    >>> nodeA = tk.randn((2, 3), network=net)
+    >>> tensorB = torch.randn(2, 3)
+    >>> result = nodeA - tensorB
+    >>> result.shape
+    torch.Size([2, 3])
     """
     return sub_op(node1, node2)
@@ -1151,13 +1543,21 @@

Source code for tensorkrowch.operations

     Element-wise subtraction between two nodes. It can also be performed using
     the operator ``-``.
     
+    It also admits to take as ``node2`` a number or tensor, that will be
+    subtracted from the ``self`` tensor as ``self.tensor - node2``. If this
+    is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method
+    of a  :class:`~tensorkrowch.TensorNetwork`, this will have to be called
+    explicitly to contract the network, rather than relying on its internal
+    call via the :meth:`~tensorkrowch.TensorNetwork.forward`.
+    
     Nodes ``resultant`` from this operation are called ``"sub"``. The node
     that keeps information about the :class:`Successor` is ``self``.
 
     Parameters
     ----------
-    node2 : Node or ParamNode
-        Second node, subtrahend.
+    node2 : Node, ParamNode, torch.Tensor or number
+        Second node, subtrahend. It can also be a number or tensor with
+        appropiate shape.
 
     Returns
     -------
@@ -1171,11 +1571,179 @@ 

Source code for tensorkrowch.operations

     >>> result = nodeA.sub(nodeB)
     >>> result.shape
     torch.Size([2, 3])
+    
+    >>> net = tk.TensorNetwork()
+    >>> nodeA = tk.randn((2, 3), network=net)
+    >>> tensorB = torch.randn(2, 3)
+    >>> result = nodeA.sub(tensorB)
+    >>> result.shape
+    torch.Size([2, 3])
     """
 
 AbstractNode.__sub__ = sub_node
 
 
+###############################   renormalize    ##############################
+# MARK: renormalize
+def _check_first_renormalize(
+    node: AbstractNode,
+    p: Union[int, float] = 2,
+    axis: Optional[Union[Ax, Sequence[Ax]]] = None) -> Optional[Successor]:
+    
+    if isinstance(axis, (tuple, list)):
+        axis = tuple(axis)
+    args = (node, p, axis)
+    successors = node._successors.get('renormalize')
+    if not successors:
+        return None
+    return successors.get(args)
+
+
+def _renormalize_first(
+    node: AbstractNode,
+    p: Union[int, float] = 2,
+    axis: Optional[Union[Ax, Sequence[Ax]]] = None) -> Node:
+    
+    axis_num = []
+    if axis is not None:
+        if isinstance(axis, (tuple, list)):
+            for ax in axis:
+                axis_num.append(node.get_axis_num(ax))
+            axis = tuple(axis)
+        else:
+            axis_num.append(node.get_axis_num(axis))
+    
+    norm = node.tensor.norm(p=p, dim=axis_num, keepdim=True)
+    new_tensor = node.tensor / norm
+    new_node = Node._create_resultant(axes_names=node.axes_names,
+                                      name='renormalize',
+                                      network=node._network,
+                                      tensor=new_tensor,
+                                      edges=node._edges,
+                                      node1_list=node.is_node1())
+
+    # Create successor
+    net = node._network
+    args = (node, p, axis)
+    successor = Successor(node_ref=node.node_ref(),
+                          index=node._tensor_info['index'],
+                          child=new_node,
+                          hints=axis_num)
+
+    # Add successor to parent
+    if 'renormalize' in node._successors:
+        node._successors['renormalize'].update({args: successor})
+    else:
+        node._successors['renormalize'] = {args: successor}
+
+    # Add operation to list of performed operations of TN
+    net._seq_ops.append(('renormalize', args))
+
+    # Record in inverse_memory while tracing
+    if net._tracing:
+        node._record_in_inverse_memory()
+
+    return new_node
+
+
+def _renormalize_next(
+    successor: Successor,
+    node: AbstractNode,
+    p: Union[int, float] = 2,
+    axis: Optional[Union[Ax, Sequence[Ax]]] = None) -> Node:
+    
+    axis_num = successor.hints
+    tensor = node._direct_get_tensor(successor.node_ref,
+                                     successor.index)
+    norm = tensor.norm(p=p, dim=axis_num, keepdim=True)
+    new_tensor = tensor / norm
+    
+    child = successor.child
+    child._direct_set_tensor(new_tensor)
+
+    # Record in inverse_memory while contracting, if network is traced
+    # (to delete memory if possible)
+    if node._network._traced:
+        node._check_inverse_memory(successor.node_ref)
+    
+    return child
+
+
+renormalize_op = Operation('renormalize',
+                           _check_first_renormalize,
+                           _renormalize_first,
+                           _renormalize_next)
+
+
+
[docs]def renormalize( + node: AbstractNode, + p: Union[int, float] = 2, + axis: Optional[Union[Ax, Sequence[Ax]]] = None) -> Node: + """ + Normalizes the node with the specified norm. That is, the tensor of ``node`` + is divided by its norm. + + Different norms can be taken, specifying the argument ``p``, and accross + different dimensions, or node axes, specifying the argument ``axis``. + + See also `torch.norm() <https://pytorch.org/docs/stable/generated/torch.norm.html>`_. + + Parameters + ---------- + node : Node or ParamNode + Node that is to be renormalized. + p : int, float + The order of the norm. + axis : int, str, Axis or list[int, str or Axis], optional + Axis or sequence of axes over which to reduce. + + Returns + ------- + Node + + Examples + -------- + >>> nodeA = tk.randn((3, 3)) + >>> renormA = tk.renormalize(nodeA) + >>> renormA.norm() + tensor(1.) + """ + return renormalize_op(node, p, axis)
+ + +renormalize_node = copy_func(renormalize) +renormalize_node.__doc__ = \ + """ + Normalizes the node with the specified norm. That is, the tensor of ``node`` + is divided by its norm. + + Different norms can be taken, specifying the argument ``p``, and accross + different dimensions, or node axes, specifying the argument ``axis``. + + See also `torch.norm() <https://pytorch.org/docs/stable/generated/torch.norm.html>`_. + + Parameters + ---------- + p : int, float + The order of the norm. + axis : int, str, Axis or list[int, str or Axis], optional + Axis or sequence of axes over which to reduce. + + Returns + ------- + Node + + Examples + -------- + >>> nodeA = tk.randn((3, 3)) + >>> renormA = nodeA.renormalize() + >>> renormA.norm() + tensor(1.) + """ + +AbstractNode.renormalize = renormalize_node + + ############################################################################### # NODE-LIKE OPERATIONS # ############################################################################### @@ -1292,7 +1860,7 @@

Source code for tensorkrowch.operations

             cp_rank = torch.lt(
                 s_percentages,
                 cum_percentage_tensor
-                ).view(-1, s.shape[-1]).all(dim=0).sum()
+                ).view(-1, s.shape[-1]).any(dim=0).sum()
             lst_ranks.append(max(1, cp_rank.item() + 1))
             
         if cutoff is not None:
@@ -1300,7 +1868,7 @@ 

Source code for tensorkrowch.operations

             co_rank = torch.ge(
                 s,
                 cutoff_tensor
-                ).view(-1, s.shape[-1]).all(dim=0).sum()
+                ).view(-1, s.shape[-1]).any(dim=0).sum()
             lst_ranks.append(max(1, co_rank.item()))
         
         # Select rank from specified restrictions
@@ -1491,7 +2059,7 @@ 

Source code for tensorkrowch.operations

             cp_rank = torch.lt(
                 s_percentages,
                 cum_percentage_tensor
-                ).view(-1, s.shape[-1]).all(dim=0).sum()
+                ).view(-1, s.shape[-1]).any(dim=0).sum()
             lst_ranks.append(max(1, cp_rank.item() + 1))
             
         if cutoff is not None:
@@ -1499,7 +2067,7 @@ 

Source code for tensorkrowch.operations

             co_rank = torch.ge(
                 s,
                 cutoff_tensor
-                ).view(-1, s.shape[-1]).all(dim=0).sum()
+                ).view(-1, s.shape[-1]).any(dim=0).sum()
             lst_ranks.append(max(1, co_rank.item()))
         
         # Select rank from specified restrictions
@@ -1953,6 +2521,10 @@ 

Source code for tensorkrowch.operations

     Contracts an edge via :func:`contract` and splits it via :func:`split`
     using ``mode = "svd"``. See :func:`split` for a more complete explanation.
     
+    This only works if the nodes connected through the edge are ``leaf`` nodes.
+    Otherwise, this will perform the contraction between the ``leaf`` nodes
+    that were connected through this edge.
+    
     This operation is the same as :meth:`~Edge.svd`.
 
     Parameters
@@ -2076,6 +2648,10 @@ 

Source code for tensorkrowch.operations

     Contracts an edge via :meth:`~Edge.contract` and splits it via
     :meth:`~AbstractNode.split` using ``mode = "svd"``. See :func:`split` for
     a more complete explanation.
+    
+    This only works if the nodes connected through the edge are ``leaf`` nodes.
+    Otherwise, this will perform the contraction between the ``leaf`` nodes
+    that were connected through this edge.
 
     Parameters
     ----------
@@ -2346,6 +2922,10 @@ 

Source code for tensorkrowch.operations

     Contracts an edge via :func:`contract` and splits it via :func:`split`
     using ``mode = "svdr"``. See :func:`split` for a more complete explanation.
     
+    This only works if the nodes connected through the edge are ``leaf`` nodes.
+    Otherwise, this will perform the contraction between the ``leaf`` nodes
+    that were connected through this edge.
+    
     This operation is the same as :meth:`~Edge.svdr`.
 
     Parameters
@@ -2469,6 +3049,10 @@ 

Source code for tensorkrowch.operations

     Contracts an edge via :meth:`~Edge.contract` and splits it via
     :meth:`~AbstractNode.split` using ``mode = "svdr"``. See :func:`split` for
     a more complete explanation.
+    
+    This only works if the nodes connected through the edge are ``leaf`` nodes.
+    Otherwise, this will perform the contraction between the ``leaf`` nodes
+    that were connected through this edge.
 
     Parameters
     ----------
@@ -2735,6 +3319,10 @@ 

Source code for tensorkrowch.operations

     Contracts an edge via :func:`contract` and splits it via :func:`split`
     using ``mode = "qr"``. See :func:`split` for a more complete explanation.
     
+    This only works if the nodes connected through the edge are ``leaf`` nodes.
+    Otherwise, this will perform the contraction between the ``leaf`` nodes
+    that were connected through this edge.
+    
     This operation is the same as :meth:`~Edge.qr`.
 
     Parameters
@@ -2836,6 +3424,10 @@ 

Source code for tensorkrowch.operations

     Contracts an edge via :meth:`~Edge.contract` and splits it via
     :meth:`~AbstractNode.split` using ``mode = "qr"``. See :func:`split` for
     a more complete explanation.
+    
+    This only works if the nodes connected through the edge are ``leaf`` nodes.
+    Otherwise, this will perform the contraction between the ``leaf`` nodes
+    that were connected through this edge.
 
     Returns
     -------
@@ -3034,6 +3626,10 @@ 

Source code for tensorkrowch.operations

     Contracts an edge via :func:`contract` and splits it via :func:`split`
     using ``mode = "rq"``. See :func:`split` for a more complete explanation.
     
+    This only works if the nodes connected through the edge are ``leaf`` nodes.
+    Otherwise, this will perform the contraction between the ``leaf`` nodes
+    that were connected through this edge.
+    
     This operation is the same as :meth:`~Edge.rq`.
 
     Parameters
@@ -3135,6 +3731,10 @@ 

Source code for tensorkrowch.operations

     Contracts an edge via :meth:`~Edge.contract` and splits it via
     :meth:`~AbstractNode.split` using ``mode = "rq"``. See :func:`split` for
     a more complete explanation.
+    
+    This only works if the nodes connected through the edge are ``leaf`` nodes.
+    Otherwise, this will perform the contraction between the ``leaf`` nodes
+    that were connected through this edge.
 
     Returns
     -------
@@ -3686,6 +4286,10 @@ 

Source code for tensorkrowch.operations

     """
     Contracts the nodes that are connected through the edge.
     
+    This only works if the nodes connected through the edge are ``leaf`` nodes.
+    Otherwise, this will perform the contraction between the ``leaf`` nodes
+    that were connected through this edge.
+    
     Nodes ``resultant`` from this operation are called ``"contract_edges"``.
     The node that keeps information about the :class:`Successor` is
     ``edge.node1``.
@@ -3725,6 +4329,10 @@ 

Source code for tensorkrowch.operations

     """
     Contracts the nodes that are connected through the edge.
     
+    This only works if the nodes connected through the edge are ``leaf`` nodes.
+    Otherwise, this will perform the contraction between the ``leaf`` nodes
+    that were connected through this edge.
+    
     Nodes ``resultant`` from this operation are called ``"contract_edges"``.
     The node that keeps information about the :class:`Successor` is
     ``self.node1``.
@@ -4320,6 +4928,13 @@ 

Source code for tensorkrowch.operations

     :meth:`~TensorNetwork.auto_stack` mode affects the computation of
     :func:`stack`.
     
+    If this operation is used several times with the same input nodes, but their
+    dimensions can change from one call to another, this will lead to undesired
+    behaviour. The network should be :meth:`~tensorkrwoch.TensorNetwork.reset`.
+    This situation should be avoided in the
+    :meth:`~tensorkrowch.TensorNetwork.contract` method. Otherwise it will fail
+    in subsequent calls to ``contract`` or :meth:`~tensorkrowch.TensorNetwork.forward`
+    
     Nodes ``resultant`` from this operation are called ``"stack"``. If this
     operation returns a ``virtual`` :class:`ParamStackNode`, it will be called
     ``"virtual_result_stack"``. See :class:AbstractNode` to learn about this
diff --git a/docs/_build/html/_sources/operations.rst.txt b/docs/_build/html/_sources/operations.rst.txt
index 6643a96..e0e530e 100644
--- a/docs/_build/html/_sources/operations.rst.txt
+++ b/docs/_build/html/_sources/operations.rst.txt
@@ -85,6 +85,10 @@ mul
 ^^^
 .. autofunction:: mul
 
+div
+^^^
+.. autofunction:: div
+
 add
 ^^^
 .. autofunction:: add
@@ -93,6 +97,10 @@ sub
 ^^^
 .. autofunction:: sub
 
+renormalize
+^^^^^^^^^^^
+.. autofunction:: renormalize
+
 
 Node-like Operations
 --------------------
diff --git a/docs/_build/html/_sources/tutorials/2_contracting_tensor_network.rst.txt b/docs/_build/html/_sources/tutorials/2_contracting_tensor_network.rst.txt
index f42b503..4c8f02b 100644
--- a/docs/_build/html/_sources/tutorials/2_contracting_tensor_network.rst.txt
+++ b/docs/_build/html/_sources/tutorials/2_contracting_tensor_network.rst.txt
@@ -93,8 +93,8 @@ compute between nodes. We can distinguish between two types of operations:
 
 a) **Tensor-like**: We refer to the operations one can compute using tensors in
    vanilla ``PyTorch`` like :func:`permute` (and the in-place variant
-   :func:`permute_`), :func:`tprod` (tensor product), :func:`mul`, :func:`add`
-   and :func:`sub`.
+   :func:`permute_`), :func:`tprod` (tensor product), :func:`mul`, :func:`div`,
+   :func:`add`, :func:`sub` and :func:`renormalize`.
 
 b) **Node-like**: We refer to the operations one will need to contract a tensor
    network. These we will explain in more detail in this section.
diff --git a/docs/_build/html/_sources/tutorials/5_subclass_tensor_network.rst.txt b/docs/_build/html/_sources/tutorials/5_subclass_tensor_network.rst.txt
index 24b4148..71e431b 100644
--- a/docs/_build/html/_sources/tutorials/5_subclass_tensor_network.rst.txt
+++ b/docs/_build/html/_sources/tutorials/5_subclass_tensor_network.rst.txt
@@ -311,11 +311,11 @@ With this, you can save your model and load it later::
 Of course, although you can create any tensor network you like, ``TensorKrowch``
 already comes with a handful of widely-known models that you can use:
 
-* :class:`MPS`
-* :class:`MPSLayer`
-* :class:`MPO`
-* :class:`PEPS`
-* :class:`Tree`
+* :class:`~tensorkrowch.models.MPS`
+* :class:`~tensorkrowch.models.MPSLayer`
+* :class:`~tensorkrowch.models.MPO`
+* :class:`~tensorkrowch.models.PEPS`
+* :class:`~tensorkrowch.models.Tree`
 
 There are also uniform and convolutional variants of the four models mentioned
 above.
diff --git a/docs/_build/html/_static/documentation_options.js b/docs/_build/html/_static/documentation_options.js
index f618920..bdd7b8a 100644
--- a/docs/_build/html/_static/documentation_options.js
+++ b/docs/_build/html/_static/documentation_options.js
@@ -1,6 +1,6 @@
 var DOCUMENTATION_OPTIONS = {
     URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
-    VERSION: '1.0.1',
+    VERSION: '1.1.0',
     LANGUAGE: 'None',
     COLLAPSE_INDEX: false,
     BUILDER: 'html',
diff --git a/docs/_build/html/api.html b/docs/_build/html/api.html
index 5e22be3..73174e2 100644
--- a/docs/_build/html/api.html
+++ b/docs/_build/html/api.html
@@ -6,7 +6,7 @@
     
     
 
-    API Reference — TensorKrowch 1.0.1 documentation
+    API Reference — TensorKrowch 1.1.0 documentation
     
   
   
@@ -381,8 +381,10 @@ 

API Referencepermute_
  • tprod
  • mul
  • +
  • div
  • add
  • sub
  • +
  • renormalize
  • Node-like Operations
      diff --git a/docs/_build/html/components.html b/docs/_build/html/components.html index e085d96..c825ba7 100644 --- a/docs/_build/html/components.html +++ b/docs/_build/html/components.html @@ -6,7 +6,7 @@ - Components — TensorKrowch 1.0.1 documentation + Components — TensorKrowch 1.1.0 documentation @@ -945,6 +945,10 @@

      AbstractNode neighbours(axis=None)[source]#

      Returns the neighbours of the node, the nodes to which it is connected.

      +

      If self is a resultant node, this will return the neighbours of +the leaf nodes from which self inherits the edges. Therefore, +one cannot check if two resultant nodes are connected by looking +into their neighbours lists. To do that, use is_connected_to().

      Parameters

      axis (int, str or Axis, optional) – Axis for which to retrieve the neighbour.

      @@ -1429,7 +1433,7 @@

      AbstractNode
      -norm(p=2, axis=None)[source]#
      +norm(p=2, axis=None, keepdim=False)[source]#

      Returns the norm of all elements in the node’s tensor. If an axis is specified, the norm is over that axis. If axis is a sequence of axes, reduce over all of them.

      @@ -1441,6 +1445,8 @@

      AbstractNode

      Return type
      @@ -1612,6 +1618,34 @@

      AbstractNode +
      +renormalize(p=2, axis=None)#
      +

      Normalizes the node with the specified norm. That is, the tensor of node +is divided by its norm.

      +

      Different norms can be taken, specifying the argument p, and accross +different dimensions, or node axes, specifying the argument axis.

      +

      See also torch.norm().

      +
      +
      Parameters
      +
        +
      • p (int, float) – The order of the norm.

      • +
      • axis (int, str, Axis or list[int, str or Axis], optional) – Axis or sequence of axes over which to reduce.

      • +
      +
      +
      Return type
      +

      Node

      +
      +
      +

      Examples

      +
      >>> nodeA = tk.randn((3, 3))
      +>>> renormA = nodeA.renormalize()
      +>>> renormA.norm()
      +tensor(1.)
      +
      +
      +

      +
      split(node1_axes, node2_axes, mode='svd', side='left', rank=None, cum_percentage=None, cutoff=None)#
      @@ -2863,6 +2897,9 @@

      Edge#<
      contract()#

      Contracts the nodes that are connected through the edge.

      +

      This only works if the nodes connected through the edge are leaf nodes. +Otherwise, this will perform the contraction between the leaf nodes +that were connected through this edge.

      Nodes resultant from this operation are called "contract_edges". The node that keeps information about the Successor is self.node1.

      @@ -2938,6 +2975,9 @@

      Edge#<

      Contracts an edge via contract() and splits it via split() using mode = "qr". See split() for a more complete explanation.

      +

      This only works if the nodes connected through the edge are leaf nodes. +Otherwise, this will perform the contraction between the leaf nodes +that were connected through this edge.

      Return type

      tuple[Node, Node]

      @@ -3028,6 +3068,9 @@

      Edge#<

      Contracts an edge via contract() and splits it via split() using mode = "rq". See split() for a more complete explanation.

      +

      This only works if the nodes connected through the edge are leaf nodes. +Otherwise, this will perform the contraction between the leaf nodes +that were connected through this edge.

      Return type

      tuple[Node, Node]

      @@ -3118,6 +3161,9 @@

      Edge#<

      Contracts an edge via contract() and splits it via split() using mode = "svd". See split() for a more complete explanation.

      +

      This only works if the nodes connected through the edge are leaf nodes. +Otherwise, this will perform the contraction between the leaf nodes +that were connected through this edge.

      Parameters

  • +

    To embed a data tensor with elements between 0 and 1 as basis vectors, one +can concatenate discretize() with basis().

    +
    >>> a = torch.rand(100, 10)
    +>>> emb_a = tk.embeddings.discretize(a, level=1, base=5)
    +>>> emb_a.shape
    +torch.Size([100, 10, 1])
    +
    +
    +
    >>> emb_a = tk.embeddings.basis(emb_a.squeeze(2).int(), dim=5)
    +>>> emb_a.shape
    +torch.Size([100, 10, 5])
    +
    +
    @@ -754,6 +767,19 @@

    basis torch.Size([100, 5, 10])

    +

    To embed a data tensor with elements between 0 and 1 as basis vectors, one +can concatenate discretize() with basis().

    +
    >>> a = torch.rand(100, 10)
    +>>> emb_a = tk.embeddings.discretize(a, level=1, base=5)
    +>>> emb_a.shape
    +torch.Size([100, 10, 1])
    +
    +
    +
    >>> emb_a = tk.embeddings.basis(emb_a.squeeze(2).int(), dim=5)
    +>>> emb_a.shape
    +torch.Size([100, 10, 5])
    +
    +
    diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html index f14cfc7..114f7e9 100644 --- a/docs/_build/html/genindex.html +++ b/docs/_build/html/genindex.html @@ -5,7 +5,7 @@ - Index — TensorKrowch 1.0.1 documentation + Index — TensorKrowch 1.1.0 documentation @@ -390,9 +390,11 @@

    B

    C

  • discretize() (in module tensorkrowch.embeddings) +
  • +
  • div() (in module tensorkrowch)
  • dtype (tensorkrowch.AbstractNode property)
  • @@ -577,6 +581,8 @@

    E

  • einsum() (in module tensorkrowch)
  • empty() (in module tensorkrowch) +
  • +
  • entropy() (tensorkrowch.models.MPS method)
  • @@ -778,8 +784,6 @@

    M

    -
  • reset() (tensorkrowch.TensorNetwork method) +
  • renormalize() (in module tensorkrowch) + +
  • +
  • reset() (tensorkrowch.TensorNetwork method)
    • +
    • reset_tensor_address() (tensorkrowch.AbstractNode method) +
    • resultant_nodes (tensorkrowch.TensorNetwork property)
    • right_node (tensorkrowch.models.MPO property) diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html index 3916bc0..0f6818d 100644 --- a/docs/_build/html/index.html +++ b/docs/_build/html/index.html @@ -6,7 +6,7 @@ - TensorKrowch documentation — TensorKrowch 1.0.1 documentation + TensorKrowch documentation — TensorKrowch 1.1.0 documentation diff --git a/docs/_build/html/initializers.html b/docs/_build/html/initializers.html index 75d01e5..f6f9f62 100644 --- a/docs/_build/html/initializers.html +++ b/docs/_build/html/initializers.html @@ -6,7 +6,7 @@ - Initializers — TensorKrowch 1.0.1 documentation + Initializers — TensorKrowch 1.1.0 documentation @@ -429,22 +429,16 @@

      Contents

      empty#

      -tensorkrowch.empty(shape, axes_names=None, name=None, network=None, param_node=False, device=None)[source]#
      +tensorkrowch.empty(shape, param_node=False, *args, **kwargs)[source]#

      Returns Node or ParamNode without tensor.

      Parameters
      • shape (list[int], tuple[int] or torch.Size) – Node’s shape, that is, the shape of its tensor.

      • -
      • axes_names (list[str] or tuple[str], optional) – Sequence of names for each of the node’s axes. Names are used to access -the edge that is attached to the node in a certain axis. Hence, they -should be all distinct.

      • -
      • name (str, optional) – Node’s name, used to access the node from de TensorNetwork -where it belongs. It cannot contain blank spaces.

      • -
      • network (TensorNetwork, optional) – Tensor network where the node should belong. If None, a new tensor -network, will be created to contain the node.

      • param_node (bool) – Boolean indicating whether the node should be a ParamNode (True) or a Node (False).

      • -
      • device (torch.device, optional) – Device where to initialize the tensor.

      • +
      • args – Arguments to initialize an AbstractNode.

      • +
      • kwargs – Keyword arguments to initialize an AbstractNode.

      Return type
      @@ -458,22 +452,16 @@

      empty

      zeros#

      -tensorkrowch.zeros(shape, axes_names=None, name=None, network=None, param_node=False, device=None)[source]#
      +tensorkrowch.zeros(shape, param_node=False, *args, **kwargs)[source]#

      Returns Node or ParamNode filled with zeros.

      Parameters
      • shape (list[int], tuple[int] or torch.Size) – Node’s shape, that is, the shape of its tensor.

      • -
      • axes_names (list[str] or tuple[str], optional) – Sequence of names for each of the node’s axes. Names are used to access -the edge that is attached to the node in a certain axis. Hence, they -should be all distinct.

      • -
      • name (str, optional) – Node’s name, used to access the node from de TensorNetwork -where it belongs. It cannot contain blank spaces.

      • -
      • network (TensorNetwork, optional) – Tensor network where the node should belong. If None, a new tensor -network, will be created to contain the node.

      • param_node (bool) – Boolean indicating whether the node should be a ParamNode (True) or a Node (False).

      • -
      • device (torch.device, optional) – Device where to initialize the tensor.

      • +
      • args – Arguments to initialize an AbstractNode.

      • +
      • kwargs – Keyword arguments to initialize an AbstractNode.

      Return type
      @@ -487,22 +475,16 @@

      zeros

      ones#

      -tensorkrowch.ones(shape=None, axes_names=None, name=None, network=None, param_node=False, device=None)[source]#
      +tensorkrowch.ones(shape, param_node=False, *args, **kwargs)[source]#

      Returns Node or ParamNode filled with ones.

      Parameters
      • shape (list[int], tuple[int] or torch.Size) – Node’s shape, that is, the shape of its tensor.

      • -
      • axes_names (list[str] or tuple[str], optional) – Sequence of names for each of the node’s axes. Names are used to access -the edge that is attached to the node in a certain axis. Hence, they -should be all distinct.

      • -
      • name (str, optional) – Node’s name, used to access the node from de TensorNetwork -where it belongs. It cannot contain blank spaces.

      • -
      • network (TensorNetwork, optional) – Tensor network where the node should belong. If None, a new tensor -network, will be created to contain the node.

      • param_node (bool) – Boolean indicating whether the node should be a ParamNode (True) or a Node (False).

      • -
      • device (torch.device, optional) – Device where to initialize the tensor.

      • +
      • args – Arguments to initialize an AbstractNode.

      • +
      • kwargs – Keyword arguments to initialize an AbstractNode.

      Return type
      @@ -516,7 +498,7 @@

      ones#<

      copy#

      -tensorkrowch.copy(shape=None, axes_names=None, name=None, network=None, param_node=False, device=None)[source]#
      +tensorkrowch.copy(shape, param_node=False, *args, **kwargs)[source]#

      Returns Node or ParamNode with a copy tensor, that is, a tensor filled with zeros except in the diagonal (elements \(T_{i_1 \ldots i_n}\) with \(i_1 = \ldots = i_n\)), which is @@ -525,16 +507,10 @@

      copy#<
      Parameters
      Return type
      @@ -548,25 +524,17 @@

      copy#<

      rand#

      -tensorkrowch.rand(shape=None, axes_names=None, name=None, network=None, param_node=False, device=None, low=0.0, high=1.0)[source]#
      +tensorkrowch.rand(shape, param_node=False, *args, **kwargs)[source]#

      Returns Node or ParamNode filled with elements drawn from a uniform distribution \(U(low, high)\).

      Parameters
      • shape (list[int], tuple[int] or torch.Size) – Node’s shape, that is, the shape of its tensor.

      • -
      • axes_names (list[str] or tuple[str], optional) – Sequence of names for each of the node’s axes. Names are used to access -the edge that is attached to the node in a certain axis. Hence, they -should be all distinct.

      • -
      • name (str, optional) – Node’s name, used to access the node from de TensorNetwork -where it belongs. It cannot contain blank spaces.

      • -
      • network (TensorNetwork, optional) – Tensor network where the node should belong. If None, a new tensor -network, will be created to contain the node.

      • param_node (bool) – Boolean indicating whether the node should be a ParamNode (True) or a Node (False).

      • -
      • device (torch.device, optional) – Device where to initialize the tensor.

      • -
      • low (float) – Lower limit of the uniform distribution.

      • -
      • high (float) – Upper limit of the uniform distribution.

      • +
      • args – Arguments to initialize an AbstractNode.

      • +
      • kwargs – Keyword arguments to initialize an AbstractNode.

      Return type
      @@ -580,25 +548,17 @@

      rand#<

      randn#

      -tensorkrowch.randn(shape=None, axes_names=None, name=None, network=None, param_node=False, device=None, mean=0.0, std=1.0)[source]#
      +tensorkrowch.randn(shape, param_node=False, *args, **kwargs)[source]#

      Returns Node or ParamNode filled with elements drawn from a normal distribution \(N(mean, std)\).

      Parameters
      • shape (list[int], tuple[int] or torch.Size) – Node’s shape, that is, the shape of its tensor.

      • -
      • axes_names (list[str] or tuple[str], optional) – Sequence of names for each of the node’s axes. Names are used to access -the edge that is attached to the node in a certain axis. Hence, they -should be all distinct.

      • -
      • name (str, optional) – Node’s name, used to access the node from de TensorNetwork -where it belongs. It cannot contain blank spaces.

      • -
      • network (TensorNetwork, optional) – Tensor network where the node should belong. If None, a new tensor -network, will be created to contain the node.

      • param_node (bool) – Boolean indicating whether the node should be a ParamNode (True) or a Node (False).

      • -
      • device (torch.device, optional) – Device where to initialize the tensor.

      • -
      • mean (float) – Mean of the normal distribution.

      • -
      • std (float) – Standard deviation of the normal distribution.

      • +
      • args – Arguments to initialize an AbstractNode.

      • +
      • kwargs – Keyword arguments to initialize an AbstractNode.

      Return type
      diff --git a/docs/_build/html/installation.html b/docs/_build/html/installation.html index 34a8762..5a16617 100644 --- a/docs/_build/html/installation.html +++ b/docs/_build/html/installation.html @@ -6,7 +6,7 @@ - Installation — TensorKrowch 1.0.1 documentation + Installation — TensorKrowch 1.1.0 documentation diff --git a/docs/_build/html/models.html b/docs/_build/html/models.html index 89c8ec2..0f4f0d5 100644 --- a/docs/_build/html/models.html +++ b/docs/_build/html/models.html @@ -6,7 +6,7 @@ - Models — TensorKrowch 1.0.1 documentation + Models — TensorKrowch 1.1.0 documentation @@ -852,9 +852,15 @@

      MPS#paper, adding identities at the top of random gaussian tensors. In this case, std should be specified with a low value, e.g., std = 1e-9.

    • -
    • "unit": Nodes are initialized as random unitaries, so that the -MPS is in canonical form, with the orthogonality center at the -rightmost node.

    • +
    • "unit": Nodes are initialized as stacks of random unitaries. This, +combined (at least) with an embedding of the inputs as elements of +the computational basis (discretize() +combined with basis())

    • +
    • "canonical"`: MPS is initialized in canonical form with a squared +norm close to the product of all the physical dimensions (if bond +dimensions are bigger than the powers of the physical dimensions, +the norm could vary). Th orthogonality center is at the rightmost +node.

    Parameters
    @@ -864,7 +870,7 @@

    MPS#boundary is "pbc", all tensors should be rank-3.

    -
  • init_method ({"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit"}, optional) – Initialization method.

  • +
  • init_method ({"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit", "canonical"}, optional) – Initialization method.

  • device (torch.device, optional) – Device where to initialize the tensors if init_method is provided.

  • kwargs (float) – Keyword arguments for the different initialization methods. See make_tensor().

  • @@ -918,7 +924,7 @@

    MPS#
    -contract(inline_input=False, inline_mats=False, marginalize_output=False, embedding_matrices=None, mpo=None)[source]#
    +contract(inline_input=False, inline_mats=False, renormalize=False, marginalize_output=False, embedding_matrices=None, mpo=None)[source]#

    Contracts the whole MPS.

    If the MPS has input nodes, these are contracted against input data nodes.

    @@ -965,6 +971,14 @@

    MPS#

    inline_mats (bool) – Boolean indicating whether the sequence of matrices (resultant after contracting the input data nodes) should be contracted inline or as a sequence of pairwise stacked contrations.

    +
  • renormalize (bool) – Indicates whether nodes should be renormalized after contraction. +If not, it may happen that the norm explodes or vanishes, as it +is being accumulated from all nodes. Renormalization aims to avoid +this undesired behavior by extracting the norm of each node on a +logarithmic scale. The renormalization only occurs when multiplying +sequences of matrices, once the input contractions have been +already performed, including contracting against embedding matrices +or MPOs when marginalize_output = True.

  • marginalize_output (bool) – Boolean indicating whether output nodes should be marginalized. If True, after contracting all the input nodes with their neighbouring data nodes, this resultant network is contracted with @@ -1005,9 +1019,9 @@

    MPS#
    -partial_density(trace_sites=[])[source]#
    +partial_density(trace_sites=[], renormalize=True)[source]#

    Returns de partial density matrix, tracing out the sites specified -by trace_sites.

    +by trace_sites: \(\rho_A\).

    This method internally sets out_features = trace_sites, and calls the forward() method with marginalize_output = True. Therefore, it may alter the behaviour @@ -1022,10 +1036,19 @@

    MPS#MPS.

    Parameters
    -

    trace_sites (list[int] or tuple[int]) – Sequence of nodes’ indices in the MPS. These indices specify the +

      +
    • trace_sites (list[int] or tuple[int]) – Sequence of nodes’ indices in the MPS. These indices specify the nodes that should be traced to compute the density matrix. If it is empty [], the total density matrix will be returned, -though this may be costly if n_features is big.

      +though this may be costly if n_features is big.

    • +
    • renormalize (bool) – Indicates whether nodes should be renormalized after contraction. +If not, it may happen that the norm explodes or vanishes, as it +is being accumulated from all nodes. Renormalization aims to avoid +this undesired behavior by extracting the norm of each node on a +logarithmic scale. The renormalization only occurs when multiplying +sequences of matrices, once the input contractions have been +already performed.

    • +

    Examples

    @@ -1040,25 +1063,30 @@

    MPS#

  • -
    -mi(middle_site, renormalize=False)[source]#
    -

    Computes the Mutual Information between subsystems \(A\) and -\(B\), \(\textrm{MI}(A:B)\), where \(A\) goes from site +

    +entropy(middle_site, renormalize=False)[source]#
    +

    Computes the reduced von Neumann Entropy between subsystems \(A\) +and \(B\), \(S(\rho_A)\), where \(A\) goes from site 0 to middle_site, and \(B\) goes from middle_site + 1 to n_features - 1.

    -

    To compute the mutual information, the MPS is put into canonical form +

    To compute the reduced entropy, the MPS is put into canonical form with orthogonality center at middle_site. Bond dimensions are not changed if possible. Only when the bond dimension is bigger than the physical dimension multiplied by the other bond dimension of the node, it will be cropped to that size.

    If the MPS is not normalized, it may happen that the computation of the -mutual information fails due to errors in the Singular Value +reduced entropy fails due to errors in the Singular Value Decompositions. To avoid this, it is recommended to set renormalize = True. In this case, the norm of each node after the SVD is extracted in logarithmic form, and accumulated. As a result, -the function will return the tuple (mi, log_norm), which is a sort -of scaled mutual information. The actual mutual information could be -obtained as exp(log_norm) * mi.

    +the function will return the tuple (entropy, log_norm), which is a +sort of scaled reduced entropy. This is, indeed, the reduced entropy +of a distribution, since the schmidt values are normalized to sum up +to 1.

    +

    The actual reduced entropy, without rescaling, could be obtained as:

    +
    +\[\exp(\texttt{log_norm})^2 \cdot S(\rho_A) - +\exp(\texttt{log_norm})^2 \cdot 2 \cdot \texttt{log_norm}\]
    Parameters
      @@ -1214,8 +1242,14 @@

      UMPS#< paper, adding identities at the top of a random gaussian tensor. In this case, std should be specified with a low value, e.g., std = 1e-9.

      -
    • "unit": Tensor is initialized as a random unitary, so that the -MPS is in canonical form.

    • +
    • "unit": Tensor is initialized as a stack of random unitaries. This, +combined (at least) with an embedding of the inputs as elements of +the computational basis (discretize() +combined with basis())

    • +
    • "canonical"`: MPS is initialized in canonical form with a squared +norm close to the product of all the physical dimensions (if bond +dimensions are bigger than the powers of the physical dimensions, +the norm could vary).

    Parameters
    @@ -1223,7 +1257,7 @@

    UMPS#<
  • tensors (list[torch.Tensor] or tuple[torch.Tensor], optional) – Sequence of a single tensor to set in each of the MPS nodes. The tensor should be rank-3, with its first and last dimensions being equal.

  • -
  • init_method ({"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit"}, optional) – Initialization method.

  • +
  • init_method ({"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit", "canonical"}, optional) – Initialization method.

  • device (torch.device, optional) – Device where to initialize the tensors if init_method is provided.

  • kwargs (float) – Keyword arguments for the different initialization methods. See make_tensor().

  • @@ -1409,9 +1443,14 @@

    MPSLayerpaper, adding identities at the top of random gaussian tensors. In this case, std should be specified with a low value, e.g., std = 1e-9.

    -
  • "unit": Nodes are initialized as random unitaries, so that the -MPS is in canonical form, with the orthogonality center at the -rightmost node.

  • +
  • "unit": Nodes are initialized as stacks of random unitaries. This, +combined (at least) with an embedding of the inputs as elements of +the computational basis (discretize() +combined with basis())

  • +
  • "canonical"`: MPS is initialized in canonical form with a squared +norm close to the product of all the physical dimensions (if bond +dimensions are bigger than the powers of the physical dimensions, +the norm could vary). Th orthogonality center is at the output node.

  • Parameters
    @@ -1421,7 +1460,7 @@

    MPSLayerboundary is "pbc", all tensors should be rank-3.

    -
  • init_method ({"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit"}, optional) – Initialization method.

  • +
  • init_method ({"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit", "canonical"}, optional) – Initialization method.

  • device (torch.device, optional) – Device where to initialize the tensors if init_method is provided.

  • kwargs (float) – Keyword arguments for the different initialization methods. See make_tensor().

  • @@ -1555,8 +1594,14 @@

    UMPSLayerpaper, adding identities at the top of a random gaussian tensor. In this case, std should be specified with a low value, e.g., std = 1e-9.

    -
  • "unit": Tensor is initialized as a random unitary, so that the -MPS is in canonical form.

  • +
  • "unit": Tensor is initialized as a stack of random unitaries. This, +combined (at least) with an embedding of the inputs as elements of +the computational basis (discretize() +combined with basis())

  • +
  • "canonical"`: MPS is initialized in canonical form with a squared +norm close to the product of all the physical dimensions (if bond +dimensions are bigger than the powers of the physical dimensions, +the norm could vary).

  • Parameters
    @@ -1565,7 +1610,7 @@

    UMPSLayerinit_method is provided.

  • kwargs (float) – Keyword arguments for the different initialization methods. See make_tensor().

  • @@ -2552,7 +2597,7 @@

    MPO#
    -contract(inline_input=False, inline_mats=False, mps=None)[source]#
    +contract(inline_input=False, inline_mats=False, renormalize=False, mps=None)[source]#

    Contracts the whole MPO with input data nodes. The input can be in the form of an MPSData, which may be convenient for tensorizing vector-matrix multiplication in the form of MPS-MPO contraction.

    @@ -2586,6 +2631,13 @@

    MPO#

    inline_mats (bool) – Boolean indicating whether the sequence of matrices (resultant after contracting the input data nodes) should be contracted inline or as a sequence of pairwise stacked contrations.

    +
  • renormalize (bool) – Indicates whether nodes should be renormalized after contraction. +If not, it may happen that the norm explodes or vanishes, as it +is being accumulated from all nodes. Renormalization aims to avoid +this undesired behavior by extracting the norm of each node on a +logarithmic scale. The renormalization only occurs when multiplying +sequences of matrices, once the input contractions have been +already performed, including contracting against MPSData.

  • mps (MPSData, optional) – MPS that is to be contracted with the MPO. New data can be put into the MPS via MPSData.add_data(), and the MPS-MPO contraction is performed by calling mpo(mps=mps_data), without @@ -2599,6 +2651,59 @@

    MPO#

  • +
    +
    +canonicalize(oc=None, mode='svd', rank=None, cum_percentage=None, cutoff=None, renormalize=False)[source]#
    +

    Turns MPO into canonical form via local SVD/QR decompositions in the +same way this transformation is applied to MPS.

    +

    To specify the new bond dimensions, the arguments rank, +cum_percentage or cutoff can be specified. These will be used +equally for all SVD computations.

    +

    If none of them are specified, the bond dimensions won’t be modified +if possible. Only when the bond dimension is bigger than the physical +dimension multiplied by the other bond dimension of the node, it will +be cropped to that size.

    +
    +
    Parameters
    +
      +
    • oc (int) – Position of the orthogonality center. It should be between 0 and +n_features - 1.

    • +
    • mode ({"svd", "svdr", "qr"}) – Indicates which decomposition should be used to split a node after +contracting it. See more at svd_(), +svdr_(), qr_(). +If mode is “qr”, operation qr_() will be +performed on nodes at the left of the output node, whilst operation +rq_() will be used for nodes at the right.

    • +
    • rank (int, optional) – Number of singular values to keep.

    • +
    • cum_percentage (float, optional) –

      Proportion that should be satisfied between the sum of all singular +values kept and the total sum of all singular values.

      +
      +\[\frac{\sum_{i \in \{kept\}}{s_i}}{\sum_{i \in \{all\}}{s_i}} \ge +cum\_percentage\]
      +

    • +
    • cutoff (float, optional) – Quantity that lower bounds singular values in order to be kept.

    • +
    • renormalize (bool) – Indicates whether nodes should be renormalized after SVD/QR +decompositions. If not, it may happen that the norm explodes as it +is being accumulated from all nodes. Renormalization aims to avoid +this undesired behavior by extracting the norm of each node on a +logarithmic scale after SVD/QR decompositions are computed. Finally, +the normalization factor is evenly distributed among all nodes of +the MPO.

    • +
    +
    +
    +

    Examples

    +
    >>> mpo = tk.models.MPO(n_features=4,
    +...                     in_dim=2,
    +...                     out_dim=2,
    +...                     bond_dim=5)
    +>>> mpo.canonicalize(rank=3)
    +>>> mpo.bond_dim
    +[3, 3, 3]
    +
    +
    +
    +

    diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv index c4de50f..3b92333 100644 Binary files a/docs/_build/html/objects.inv and b/docs/_build/html/objects.inv differ diff --git a/docs/_build/html/operations.html b/docs/_build/html/operations.html index d35054a..48ef3b9 100644 --- a/docs/_build/html/operations.html +++ b/docs/_build/html/operations.html @@ -6,7 +6,7 @@ - Operations — TensorKrowch 1.0.1 documentation + Operations — TensorKrowch 1.1.0 documentation @@ -436,6 +436,11 @@ mul +
  • + + div + +
  • add @@ -446,6 +451,11 @@ sub
  • +
  • + + renormalize + +
  • @@ -621,6 +631,11 @@

    Contents

    mul
  • +
  • + + div + +
  • add @@ -631,6 +646,11 @@

    Contents

    sub
  • +
  • + + renormalize + +
  • @@ -795,6 +815,9 @@

    svd#tensorkrowch.svd(edge, side='left', rank=None, cum_percentage=None, cutoff=None)[source]#

    Contracts an edge via contract() and splits it via split() using mode = "svd". See split() for a more complete explanation.

    +

    This only works if the nodes connected through the edge are leaf nodes. +Otherwise, this will perform the contraction between the leaf nodes +that were connected through this edge.

    This operation is the same as svd().

    Parameters
    @@ -928,6 +951,9 @@

    svdr#< tensorkrowch.svdr(edge, side='left', rank=None, cum_percentage=None, cutoff=None)[source]#

    Contracts an edge via contract() and splits it via split() using mode = "svdr". See split() for a more complete explanation.

    +

    This only works if the nodes connected through the edge are leaf nodes. +Otherwise, this will perform the contraction between the leaf nodes +that were connected through this edge.

    This operation is the same as svdr().

    Parameters
    @@ -1061,6 +1087,9 @@

    qr#< tensorkrowch.qr(edge)[source]#

    Contracts an edge via contract() and splits it via split() using mode = "qr". See split() for a more complete explanation.

    +

    This only works if the nodes connected through the edge are leaf nodes. +Otherwise, this will perform the contraction between the leaf nodes +that were connected through this edge.

    This operation is the same as qr().

    Parameters
    @@ -1164,6 +1193,9 @@

    rq#< tensorkrowch.rq(edge)[source]#

    Contracts an edge via contract() and splits it via split() using mode = "rq". See split() for a more complete explanation.

    +

    This only works if the nodes connected through the edge are leaf nodes. +Otherwise, this will perform the contraction between the leaf nodes +that were connected through this edge.

    This operation is the same as rq().

    Parameters
    @@ -1266,6 +1298,9 @@

    contract tensorkrowch.contract(edge)[source]#

    Contracts the nodes that are connected through the edge.

    +

    This only works if the nodes connected through the edge are leaf nodes. +Otherwise, this will perform the contraction between the leaf nodes +that were connected through this edge.

    Nodes resultant from this operation are called "contract_edges". The node that keeps information about the Successor is edge.node1.

    @@ -1487,13 +1522,20 @@

    mul#tensorkrowch.mul(node1, node2)[source]#

    Element-wise product between two nodes. It can also be performed using the operator *.

    +

    It also admits to take as node2 a number or tensor, that will be +multiplied by the node1 tensor as node1.tensor * node2. If this +is used like this in the contract() method +of a TensorNetwork, this will have to be called +explicitly to contract the network, rather than relying on its internal +call via the forward().

    Nodes resultant from this operation are called "mul". The node that keeps information about the Successor is node1.

    Parameters
    • node1 (Node or ParamNode) – First node to be multiplied. Its edges will appear in the resultant node.

    • -
    • node2 (Node or ParamNode) – Second node to be multiplied.

    • +
    • node2 (Node, ParamNode, torch.Tensor or number) – Second node to be multiplied. It can also be a number or tensor with +appropiate shape.

    Return type
    @@ -1509,6 +1551,61 @@

    mul#torch.Size([2, 3])

  • +
    >>> net = tk.TensorNetwork()
    +>>> nodeA = tk.randn((2, 3), network=net)
    +>>> tensorB = torch.randn(2, 3)
    +>>> result = nodeA * tensorB
    +>>> result.shape
    +torch.Size([2, 3])
    +
    +
    + + + +
    +

    div#

    +
    +
    +tensorkrowch.div(node1, node2)[source]#
    +

    Element-wise division between two nodes. It can also be performed using the +operator /.

    +

    It also admits to take as node2 a number or tensor, that will +divide the node1 tensor as node1.tensor / node2. If this +is used like this in the contract() method +of a TensorNetwork, this will have to be called +explicitly to contract the network, rather than relying on its internal +call via the forward().

    +

    Nodes resultant from this operation are called "div". The node +that keeps information about the Successor is node1.

    +
    +
    Parameters
    +
      +
    • node1 (Node or ParamNode) – First node to be divided. Its edges will appear in the resultant node.

    • +
    • node2 (Node, ParamNode, torch.Tensor or number) – Second node, the divisor. It can also be a number or tensor with +appropiate shape.

    • +
    +
    +
    Return type
    +

    Node

    +
    +
    +

    Examples

    +
    >>> net = tk.TensorNetwork()
    +>>> nodeA = tk.randn((2, 3), network=net)
    +>>> nodeB = tk.randn((2, 3), network=net)
    +>>> result = nodeA / nodeB
    +>>> result.shape
    +torch.Size([2, 3])
    +
    +
    +
    >>> net = tk.TensorNetwork()
    +>>> nodeA = tk.randn((2, 3), network=net)
    +>>> tensorB = torch.randn(2, 3)
    +>>> result = nodeA / tensorB
    +>>> result.shape
    +torch.Size([2, 3])
    +
    +
    @@ -1519,13 +1616,20 @@

    add#tensorkrowch.add(node1, node2)[source]#

    Element-wise addition between two nodes. It can also be performed using the operator +.

    +

    It also admits to take as node2 a number or tensor, that will be +added to the node1 tensor as node1.tensor + node2. If this +is used like this in the contract() method +of a TensorNetwork, this will have to be called +explicitly to contract the network, rather than relying on its internal +call via the forward().

    Nodes resultant from this operation are called "add". The node that keeps information about the Successor is node1.

    Parameters
    • node1 (Node or ParamNode) – First node to be added. Its edges will appear in the resultant node.

    • -
    • node2 (Node or ParamNode) – Second node to be added.

    • +
    • node2 (Node, ParamNode, torch.Tensor or numeric) – Second node to be added. It can also be a number or tensor with +appropiate shape.

    Return type
    @@ -1541,6 +1645,14 @@

    add#torch.Size([2, 3])

    +
    >>> net = tk.TensorNetwork()
    +>>> nodeA = tk.randn((2, 3), network=net)
    +>>> tensorB = torch.randn(2, 3)
    +>>> result = nodeA + tensorB
    +>>> result.shape
    +torch.Size([2, 3])
    +
    +
    @@ -1551,13 +1663,20 @@

    sub#tensorkrowch.sub(node1, node2)[source]#

    Element-wise subtraction between two nodes. It can also be performed using the operator -.

    +

    It also admits to take as node2 a number or tensor, that will be +subtracted from the node1 tensor as node1.tensor - node2. If this +is used like this in the contract() method +of a TensorNetwork, this will have to be called +explicitly to contract the network, rather than relying on its internal +call via the forward().

    Nodes resultant from this operation are called "sub". The node that keeps information about the Successor is node1.

    Parameters
    • node1 (Node or ParamNode) – First node, minuend . Its edges will appear in the resultant node.

    • -
    • node2 (Node or ParamNode) – Second node, subtrahend.

    • +
    • node2 (Node, ParamNode, torch.Tensor or number) – Second node, subtrahend. It can also be a number or tensor with +appropiate shape.

    Return type
    @@ -1573,6 +1692,46 @@

    sub#torch.Size([2, 3])

    +
    >>> net = tk.TensorNetwork()
    +>>> nodeA = tk.randn((2, 3), network=net)
    +>>> tensorB = torch.randn(2, 3)
    +>>> result = nodeA - tensorB
    +>>> result.shape
    +torch.Size([2, 3])
    +
    +
    + + + +
    +

    renormalize#

    +
    +
    +tensorkrowch.renormalize(node, p=2, axis=None)[source]#
    +

    Normalizes the node with the specified norm. That is, the tensor of node +is divided by its norm.

    +

    Different norms can be taken, specifying the argument p, and accross +different dimensions, or node axes, specifying the argument axis.

    +

    See also torch.norm().

    +
    +
    Parameters
    +
      +
    • node (Node or ParamNode) – Node that is to be renormalized.

    • +
    • p (int, float) – The order of the norm.

    • +
    • axis (int, str, Axis or list[int, str or Axis], optional) – Axis or sequence of axes over which to reduce.

    • +
    +
    +
    Return type
    +

    Node

    +
    +
    +

    Examples

    +
    >>> nodeA = tk.randn((3, 3))
    +>>> renormA = tk.renormalize(nodeA)
    +>>> renormA.norm()
    +tensor(1.)
    +
    +
    @@ -1907,6 +2066,12 @@

    stack

    See ParamStackNode and TensorNetwork to learn how the auto_stack() mode affects the computation of stack().

    +

    If this operation is used several times with the same input nodes, but their +dimensions can change from one call to another, this will lead to undesired +behaviour. The network should be reset(). +This situation should be avoided in the +contract() method. Otherwise it will fail +in subsequent calls to contract or forward()

    Nodes resultant from this operation are called "stack". If this operation returns a virtual ParamStackNode, it will be called "virtual_result_stack". See :class:AbstractNode` to learn about this diff --git a/docs/_build/html/search.html b/docs/_build/html/search.html index d25b0e2..9027654 100644 --- a/docs/_build/html/search.html +++ b/docs/_build/html/search.html @@ -5,7 +5,7 @@ - Search — TensorKrowch 1.0.1 documentation + Search — TensorKrowch 1.1.0 documentation diff --git a/docs/_build/html/searchindex.js b/docs/_build/html/searchindex.js index 353d5d8..2d84240 100644 --- a/docs/_build/html/searchindex.js +++ b/docs/_build/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["api","components","contents","decompositions","embeddings","index","initializers","installation","models","operations","tutorials","tutorials/0_first_steps","tutorials/1_creating_tensor_network","tutorials/2_contracting_tensor_network","tutorials/3_memory_management","tutorials/4_types_of_nodes","tutorials/5_subclass_tensor_network","tutorials/6_mix_with_pytorch"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":5,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.viewcode":1,sphinx:56},filenames:["api.rst","components.rst","contents.rst","decompositions.rst","embeddings.rst","index.rst","initializers.rst","installation.rst","models.rst","operations.rst","tutorials.rst","tutorials/0_first_steps.rst","tutorials/1_creating_tensor_network.rst","tutorials/2_contracting_tensor_network.rst","tutorials/3_memory_management.rst","tutorials/4_types_of_nodes.rst","tutorials/5_subclass_tensor_network.rst","tutorials/6_mix_with_pytorch.rst"],objects:{"tensorkrowch.AbstractNode":[[1,1,1,"","axes"],[1,1,1,"","axes_names"],[1,2,1,"","contract_between"],[1,2,1,"","contract_between_"],[1,1,1,"","device"],[1,2,1,"","disconnect"],[1,1,1,"","dtype"],[1,1,1,"","edges"],[1,2,1,"","get_axis"],[1,2,1,"","get_axis_num"],[1,2,1,"","get_edge"],[1,2,1,"","in_which_axis"],[1,2,1,"","is_connected_to"],[1,2,1,"","is_data"],[1,2,1,"","is_leaf"],[1,2,1,"","is_node1"],[1,2,1,"","is_resultant"],[1,2,1,"","is_virtual"],[1,2,1,"","make_tensor"],[1,2,1,"","mean"],[1,2,1,"","move_to_network"],[1,1,1,"","name"],[1,2,1,"","neighbours"],[1,1,1,"","network"],[1,2,1,"","node_ref"],[1,2,1,"","norm"],[1,2,1,"","numel"],[1,2,1,"","permute"],[1,2,1,"","permute_"],[1,1,1,"","rank"],[1,2,1,"","reattach_edges"],[1,2,1,"","reset_tensor_address"],[1,2,1,"","set_tensor"],[1,2,1,"","set_tensor_from"],[1,1,1,"","shape"],[1,2,1,"","size"],[1,2,1,"","split"],[1,2,1,"","split_"],[1,2,1,"","std"],[1,1,1,"","successors"],[1,2,1,"","sum"],[1,1,1,"","tensor"],[1,2,1,"","tensor_address"],[1,2,1,"","unset_tensor"]],"tensorkrowch.Axis":[[1,2,1,"","is_batch"],[1,2,1,"","is_node1"],[1,1,1,"","name"],[1,1,1,"","node"],[1,1,1,"","num"]],"tensorkrowch.Edge":[[1,1,1,"","axes"],[1,1,1,"","axis1"],[1,1,1,"","axis2"],[1,2,1,"","change_size"],[1,2,1,"","connect"],[1,2,1,"","contract"],[1,2,1,"","contract_"],[1,2,1,"","copy"],[1,2,1,"","disconnect"],[1,2,1,"","is_attached_to"],[1,2,1,"","is_batch"],[1,2,1,"","is_dangling"],[1,1,1,"","name"],[1,1,1,"","node1"],[1,1,1,"","node2"],[1,1,1,"","nodes"],[1,2,1,"","qr"],[1,2,1,"","qr_"],[1,2,1,"","rq"],[1,2,1,"","rq_"],[1,2,1,"","size"],[1,2,1,"","svd"],[1,2,1,"","svd_"],[1,2,1,"","svdr"],[1,2,1,"","svdr_"]],"tensorkrowch.Node":[[1,2,1,"","change_type"],[1,2,1,"","copy"],[1,2,1,"","parameterize"]],"tensorkrowch.ParamNode":[[1,2,1,"","change_type"],[1,2,1,"","copy"],[1,1,1,"","grad"],[1,2,1,"","parameterize"]],"tensorkrowch.ParamStackNode":[[1,1,1,"","edges_dict"],[1,1,1,"","node1_lists_dict"],[1,2,1,"","reconnect"],[1,2,1,"","unbind"]],"tensorkrowch.StackEdge":[[1,2,1,"","connect"],[1,1,1,"","edges"],[1,1,1,"","node1_list"]],"tensorkrowch.StackNode":[[1,1,1,"","edges_dict"],[1,1,1,"","node1_lists_dict"],[1,2,1,"","reconnect"],[1,2,1,"","unbind"]],"tensorkrowch.TensorNetwork":[[1,2,1,"","add_data"],[1,1,1,"","auto_stack"],[1,1,1,"","auto_unbind"],[1,2,1,"","contract"],[1,2,1,"","copy"],[1,1,1,"","data_nodes"],[1,2,1,"","delete_node"],[1,1,1,"","edges"],[1,2,1,"","forward"],[1,1,1,"","leaf_nodes"],[1,1,1,"","nodes"],[1,1,1,"","nodes_names"],[1,2,1,"","parameterize"],[1,2,1,"","reset"],[1,1,1,"","resultant_nodes"],[1,2,1,"","set_data_nodes"],[1,2,1,"","trace"],[1,2,1,"","unset_data_nodes"],[1,1,1,"","virtual_nodes"]],"tensorkrowch.decompositions":[[3,3,1,"","mat_to_mpo"],[3,3,1,"","vec_to_mps"]],"tensorkrowch.embeddings":[[4,3,1,"","add_ones"],[4,3,1,"","basis"],[4,3,1,"","discretize"],[4,3,1,"","poly"],[4,3,1,"","unit"]],"tensorkrowch.models":[[8,0,1,"","ConvMPS"],[8,0,1,"","ConvMPSLayer"],[8,0,1,"","ConvPEPS"],[8,0,1,"","ConvTree"],[8,0,1,"","ConvUMPS"],[8,0,1,"","ConvUMPSLayer"],[8,0,1,"","ConvUPEPS"],[8,0,1,"","ConvUTree"],[8,0,1,"","MPO"],[8,0,1,"","MPS"],[8,0,1,"","MPSData"],[8,0,1,"","MPSLayer"],[8,0,1,"","PEPS"],[8,0,1,"","Tree"],[8,0,1,"","UMPO"],[8,0,1,"","UMPS"],[8,0,1,"","UMPSLayer"],[8,0,1,"","UPEPS"],[8,0,1,"","UTree"]],"tensorkrowch.models.ConvMPS":[[8,2,1,"","copy"],[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvMPSLayer":[[8,2,1,"","copy"],[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","out_channels"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvPEPS":[[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvTree":[[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvUMPS":[[8,2,1,"","copy"],[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvUMPSLayer":[[8,2,1,"","copy"],[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","out_channels"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvUPEPS":[[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvUTree":[[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.MPO":[[8,1,1,"","bond_dim"],[8,1,1,"","boundary"],[8,2,1,"","contract"],[8,2,1,"","copy"],[8,1,1,"","in_dim"],[8,2,1,"","initialize"],[8,1,1,"","left_node"],[8,1,1,"","mats_env"],[8,1,1,"","n_batches"],[8,1,1,"","n_features"],[8,1,1,"","out_dim"],[8,2,1,"","parameterize"],[8,1,1,"","right_node"],[8,2,1,"","set_data_nodes"]],"tensorkrowch.models.MPS":[[8,1,1,"","bond_dim"],[8,1,1,"","boundary"],[8,2,1,"","canonicalize"],[8,2,1,"","canonicalize_univocal"],[8,2,1,"","contract"],[8,2,1,"","copy"],[8,1,1,"","in_env"],[8,1,1,"","in_features"],[8,1,1,"","in_regions"],[8,2,1,"","initialize"],[8,1,1,"","left_node"],[8,1,1,"","mats_env"],[8,2,1,"","mi"],[8,1,1,"","n_batches"],[8,1,1,"","n_features"],[8,2,1,"","norm"],[8,1,1,"","out_env"],[8,1,1,"","out_features"],[8,1,1,"","out_regions"],[8,2,1,"","parameterize"],[8,2,1,"","partial_density"],[8,1,1,"","phys_dim"],[8,1,1,"","right_node"],[8,2,1,"","set_data_nodes"]],"tensorkrowch.models.MPSData":[[8,2,1,"","add_data"],[8,1,1,"","bond_dim"],[8,1,1,"","boundary"],[8,2,1,"","initialize"],[8,1,1,"","left_node"],[8,1,1,"","mats_env"],[8,1,1,"","n_batches"],[8,1,1,"","n_features"],[8,1,1,"","phys_dim"],[8,1,1,"","right_node"]],"tensorkrowch.models.MPSLayer":[[8,2,1,"","copy"],[8,1,1,"","in_dim"],[8,2,1,"","initialize"],[8,1,1,"","out_dim"],[8,1,1,"","out_node"],[8,1,1,"","out_position"]],"tensorkrowch.models.PEPS":[[8,1,1,"","bond_dim"],[8,1,1,"","boundary"],[8,2,1,"","contract"],[8,1,1,"","in_dim"],[8,2,1,"","initialize"],[8,1,1,"","n_batches"],[8,1,1,"","n_cols"],[8,1,1,"","n_rows"],[8,2,1,"","set_data_nodes"]],"tensorkrowch.models.Tree":[[8,1,1,"","bond_dim"],[8,2,1,"","canonicalize"],[8,2,1,"","contract"],[8,2,1,"","initialize"],[8,1,1,"","n_batches"],[8,2,1,"","set_data_nodes"],[8,1,1,"","sites_per_layer"]],"tensorkrowch.models.UMPO":[[8,2,1,"","copy"],[8,2,1,"","initialize"],[8,2,1,"","parameterize"]],"tensorkrowch.models.UMPS":[[8,2,1,"","copy"],[8,2,1,"","initialize"],[8,2,1,"","parameterize"]],"tensorkrowch.models.UMPSLayer":[[8,2,1,"","copy"],[8,1,1,"","in_dim"],[8,2,1,"","initialize"],[8,1,1,"","out_dim"],[8,1,1,"","out_node"],[8,1,1,"","out_position"],[8,2,1,"","parameterize"]],"tensorkrowch.models.UPEPS":[[8,1,1,"","bond_dim"],[8,2,1,"","contract"],[8,1,1,"","in_dim"],[8,2,1,"","initialize"],[8,1,1,"","n_batches"],[8,1,1,"","n_cols"],[8,1,1,"","n_rows"],[8,2,1,"","set_data_nodes"]],"tensorkrowch.models.UTree":[[8,1,1,"","bond_dim"],[8,2,1,"","contract"],[8,2,1,"","initialize"],[8,1,1,"","n_batches"],[8,2,1,"","set_data_nodes"],[8,1,1,"","sites_per_layer"]],tensorkrowch:[[1,0,1,"","AbstractNode"],[1,0,1,"","Axis"],[1,0,1,"","Edge"],[1,0,1,"","Node"],[9,0,1,"","Operation"],[1,0,1,"","ParamNode"],[1,0,1,"","ParamStackNode"],[1,0,1,"","StackEdge"],[1,0,1,"","StackNode"],[1,0,1,"","Successor"],[1,0,1,"","TensorNetwork"],[9,3,1,"","add"],[9,3,1,"","connect"],[9,3,1,"","connect_stack"],[9,3,1,"","contract"],[9,3,1,"","contract_"],[9,3,1,"","contract_between"],[9,3,1,"","contract_between_"],[9,3,1,"","contract_edges"],[6,3,1,"","copy"],[9,3,1,"","disconnect"],[9,3,1,"","einsum"],[6,3,1,"","empty"],[9,3,1,"","mul"],[6,3,1,"","ones"],[9,3,1,"","permute"],[9,3,1,"","permute_"],[9,3,1,"","qr"],[9,3,1,"","qr_"],[6,3,1,"","rand"],[6,3,1,"","randn"],[9,3,1,"","rq"],[9,3,1,"","rq_"],[9,3,1,"","split"],[9,3,1,"","split_"],[9,3,1,"","stack"],[9,3,1,"","stacked_einsum"],[9,3,1,"","sub"],[9,3,1,"","svd"],[9,3,1,"","svd_"],[9,3,1,"","svdr"],[9,3,1,"","svdr_"],[9,3,1,"","tprod"],[9,3,1,"","unbind"],[6,3,1,"","zeros"]]},objnames:{"0":["py","class","Python class"],"1":["py","property","Python property"],"2":["py","method","Python method"],"3":["py","function","Python function"]},objtypes:{"0":"py:class","1":"py:property","2":"py:method","3":"py:function"},terms:{"0":[1,3,4,5,6,8,9,11,13,15,16,17],"00":4,"0000":4,"0000e":4,"0086":1,"0101":1,"0188":1,"0234":1,"0371":1,"0412":1,"0440":1,"0445":1,"0465":1,"0501":1,"0521":1,"0633":1,"0639":11,"0704":1,"0728":11,"0743":1,"0750":11,"08":4,"0806":1,"0820":11,"08595":5,"09":8,"0913":11,"0983":11,"0998":11,"1":[1,3,4,5,6,8,9,17],"10":[1,4,5,8,9,11,13,16,17],"100":[1,4,5,8,9,12,13,14,15],"10000":[11,17],"1024":[11,17],"1091":1,"1123":11,"1211":1,"1308":11,"1371":1,"14":17,"1496":1,"15":[1,9,13],"1573":11,"161204":11,"1654":1,"1878":17,"1955":11,"1990":11,"1998":1,"1e":[8,11,16,17],"2":[1,4,8,9,17],"20":[1,8,9,17],"2052":17,"2091":1,"2095":1,"2111":1,"2203":17,"2254":1,"2306":5,"2316":1,"2357":17,"236220":11,"2380":1,"2390":1,"2449":1,"2461":1,"2477":1,"2503":1,"2517":1,"2618":17,"2730":1,"2775":1,"2799":1,"28":[11,17],"2808":1,"2840":17,"2856":1,"2866":1,"2898":1,"2d":8,"3":[1,4,5,8,9,15,17],"30":17,"3083":11,"3088":1,"3139":1,"3149":17,"32":[8,11],"3222":1,"3340":1,"3370":1,"3371":1,"3381":1,"3393":1,"3427":1,"3489":1,"3508":1,"3513":1,"3585":1,"3711e":4,"3714":17,"3760":1,"3784":1,"3821":1,"4":[1,4,8,9,13,15,17],"40":17,"4005":1,"4181":1,"4184":1,"4216":1,"4383":1,"4385":1,"4402":1,"4431":1,"4461":1,"4572":1,"4588":1,"4731":1,"4761":1,"4974":1,"499320":17,"4f":[11,17],"5":[1,4,5,8,9,12,13,14,15,17],"50":17,"500":[5,11,16,17],"5000":4,"5021":1,"5029":1,"5069":1,"5161":1,"5224":17,"5401":1,"5406":1,"553186":17,"5567":1,"5570":1,"5676":11,"5731":1,"5760":1,"5797":1,"5920":1,"6":[1,4,8,17],"60":17,"60000":[11,17],"6023":1,"6149":11,"6225":1,"6227":1,"6295":1,"6356":1,"6399":1,"6492":1,"6495":1,"6524":1,"6545":1,"6551":1,"6811":1,"6925":1,"6982":1,"7":[1,5,8,9,13,14,17],"70":17,"75":4,"7500":4,"7592":1,"7688":1,"7752":1,"7812":1,"7997":1,"8":[5,8,11],"80":17,"8090":1,"8147":1,"8227":1,"8361":1,"8387":1,"8441":1,"8442":1,"8502":17,"8627":17,"8633":1,"8649":1,"8795":17,"8815":1,"8820":11,"8848":17,"8851":17,"8859":1,"8901":17,"8911":1,"8915":17,"8948":17,"8968":17,"8984":17,"9":[5,8,11,16,17],"90":17,"9006":1,"9009":17,"9011":[11,17],"9026":17,"9048":17,"9053":11,"9125":17,"9145":1,"9174":17,"9231":17,"9265":1,"9284":17,"9320":1,"9371":11,"9396":11,"9400":11,"9432":1,"9509":11,"9526":11,"9551":1,"9561":1,"9585":11,"9600":11,"9618":1,"9621":11,"9625":11,"9668":11,"9677":11,"9696":11,"9700":11,"9721":11,"9729":11,"9731":11,"9734":11,"9736":11,"9738":11,"9743":11,"9768":11,"9775":11,"9793":11,"98":17,"9844":1,"99":11,"9925":1,"9942":1,"9957":1,"abstract":1,"boolean":[1,6,8],"byte":[11,17],"case":[1,3,8,14,15,16],"class":[0,1,8,11,13,16,17],"default":[1,4,8,14,15],"do":[1,8,11,12,14,15,16],"final":[1,8,13,16],"float":[1,3,6,8,9],"function":[1,8,9,13,17],"garc\u00eda":5,"import":[1,5,12,13,14,15,16,17],"int":[1,3,4,6,8,9],"n\u00ba":[11,17],"new":[1,4,6,8,9,11,12,13,14,16,17],"p\u00e9rez":5,"return":[1,3,4,6,8,9,11,12,13,14,16,17],"super":[1,16,17],"true":[1,5,6,8,9,11,13,14,15,16,17],"try":[13,17],"while":[1,5,13,14,15],A:[1,5,8,9,12],And:[14,17],As:[5,8,11,12,13,14],At:[1,12],Be:[1,13],But:[13,14,16,17],By:[1,4,5,8,11,12],For:[1,8,9,12,13,14,15,16],If:[1,3,4,5,6,8,9,12,14,16],In:[1,3,5,8,9,11,12,13,14,15,16,17],Is:8,It:[1,3,5,6,8,9,11,12,13,17],Its:[1,9],Not:16,Of:[14,16],On:1,One:[1,12],That:[1,3,4,8,9,13,14,16,17],The:[1,3,4,5,8,9,10,12,13,14,17],Then:[1,8,14],There:[1,8,13,15,16],These:[1,3,5,8,12,13,15],To:[1,3,5,7,8,9,11,12,13,14,15,16],With:[1,5,9,12,13,16],_:[1,8,9,11,12,16,17],__call__:1,__init__:[1,16,17],_channel:8,_copi:1,_dim:1,_percentag:[1,3,8,9],_size:8,_size_0:8,_size_1:8,_size_:1,a_:9,abil:5,abl:[1,12,13,16],about:[1,9,12,13,14,15,16],abov:[1,12,16],abstractnod:[0,9],acc:[11,17],acceler:1,accept:13,access:[1,6,12,13,14],accomplish:[1,3,8,15],accord:[1,9],accordingli:8,account:[1,8,15],accumul:8,accuraci:[11,17],achiev:1,act:[1,8,12,14],action:1,activ:1,actual:[1,8,12,13,14,17],ad:[1,4,8,9,13,16],adam:[11,17],adapt:9,add:[0,1,8,11,13,16,17],add_data:[1,8,15,16],add_on:[0,17],addit:[1,8,9],addition:1,address:[1,12,14],admit:8,advanc:[1,5,10],advantag:[14,16],affect:[1,9],after:[1,8,11,17],afterward:[1,8,9],again:[8,11,14,16],against:8,aim:[8,11],alejandro:5,algorithm:[1,8,14,16,17],all:[1,3,4,6,8,9,11,12,13,14,15,16,17],allow:[1,8],almost:[1,13,14,15,17],along:[1,9],alreadi:[1,8,9,11,12,13,16],also:[1,3,5,7,8,9,11,12,13,14,15,16],alter:8,although:[1,12,13,14,15,16],alwai:[1,5,8,9,11,12,13,14,15,16],among:8,amount:[9,11],an:[1,3,4,5,8,9,11,12,13,14,15,16],ancillari:[1,14,15],ani:[1,8,9,12,13,16],anoth:[1,8,9,12,13],anymor:1,api:2,appear:[1,9,12],append:[13,14,15,16,17],appli:[1,9,13],approach:5,appropi:[8,16,17],approxim:1,ar:[1,3,4,5,6,7,8,9,11,12,13,15,16,17],arang:4,arbitrari:1,architectur:5,archiveprefix:5,arg:[1,8],argument:[1,3,8,9,16],aros:1,around:11,arrai:12,arrow:9,arxiv:5,assert:[1,5,8,9,12,13,14,15],assign:8,assum:[1,4,8,9],attach:[1,6,9,12],attribut:[1,8,16],author:5,auto_stack:[1,9,11,14,15,16,17],auto_unbind:[1,9,11,14,16,17],automat:[1,9,12,13,14],auxiliari:9,avail:[5,11,12],avoid:[1,8,14],awar:[1,13],ax:[1,6,8,9,12,13],axes_nam:[1,5,6,9,12,13,14,15,16],axi:[0,4,6,9,12,16,17],axis1:1,axis2:1,axis_0:1,axis_1:1,axis_2:1,axis_n:1,b:[4,8,9,13],b_1:3,b_:9,b_m:3,backpropag:[11,17],backward:[1,5,11,13,17],base:[1,4,8,9,13],basi:[0,8],basic:[1,5,12,13],batch:[1,3,4,5,8,9,11,12,13,15,16],batch_0:[4,8],batch_1:[1,8,16],batch_m:1,batch_n:[4,8,16],batch_siz:[8,11,17],becaus:[1,8,14,17],becom:[1,5,8,9],been:[1,8,9,12,16],befor:[1,8,11,12,16,17],beforehand:12,begin:[4,11,12,17],behav:[1,14],behavior:8,behaviour:[1,8,12,14,15],being:[1,4,8,9,13],belong:[1,6,8,11,12,13],besid:[1,8,9,12],best:5,better:12,between:[1,3,4,5,8,9,12,14,15,17],big:[8,13],bigger:8,binom:4,blank:[1,6],bmatrix:4,bodi:12,bond:[3,8],bond_dim:[5,8,11,17],bool:[1,6,8],border:8,both:[1,8,9,12,13,14,15,16],bottom:[8,17],boudari:8,bound:[1,3,8,9],boundari:[3,8],bracket:1,bring:11,build:[1,5,10,13],built:[5,11,13,16],bunch:[1,13,16],c_:9,cach:[1,14],calcul:14,call:[1,8,9,11,13,16,17],callabl:9,can:[1,3,5,7,8,9,11,12,13,14,15,16,17],cannot:[1,6,8,9,13,15],canon:[8,11,17],canonic:[8,11,17],canonicalize_univoc:8,carri:[1,8,9,12],cast:1,cat:17,caus:8,cdot:[3,4,8],center:8,central:[1,12,17],certain:[1,4,5,6,9],chang:[1,5,8,12,14,16,17],change_s:1,change_typ:1,channel:8,charact:1,check:[1,8,9,11,12,13,14,15,17],check_first:9,child:1,chose:15,classif:[8,11],classifi:[11,16],clone:[5,7,8],close:8,cnn:17,cnn_snake:17,cnn_snakesb:17,co:4,code:[1,5,12,17],coincid:[8,9,12],collect:[1,9,13,14],column:8,com:[5,7],combin:[1,12,16,17],come:[1,9,11,12,15,16],comma:9,command:[5,7],common:[1,8,11,16],compar:14,complementari:8,complet:[1,9],compon:[0,4,5,9,11,13],compos:[11,17],comput:[1,4,5,7,8,9,11,13,14,16,17],concept:5,condit:[3,8,9],configur:5,conform:1,connect:[0,1,5,8,12,13,14,15,16],connect_stack:0,consecut:[3,8],consid:8,consist:[1,9],construct:[1,5,13,14],contain:[1,6,8,12,13,14,15],continu:11,contract:[0,1,5,8,10,11,12,14,15,16],contract_:[0,1,13],contract_between:[0,1,13],contract_between_:[0,1,13],contract_edg:[0,1,13],contract_edges_ip:[1,9],contrast:8,contrat:8,control:[1,12,14],conv2d:[8,17],conv_mp:8,conv_mps_lay:8,conv_pep:8,conv_tre:8,conveni:[8,15],convent:[1,9,13,16],convmp:0,convmpslay:[0,17],convolut:[8,16,17],convpep:0,convtre:0,convump:0,convumpslay:0,convupep:0,convutre:0,copi:[0,1,8],core:[8,12,17],corner:8,correct:[1,14],correctli:9,correspond:[1,3,4,8,9,12,13,14,16],costli:[1,8,14],could:[8,11,12,14,16],count:11,coupl:[1,12,15],cours:[14,16],cpu:[1,11,16,17],creat:[1,5,6,8,9,10,13,14,15,16],creation:[5,9],crop:[1,8],crossentropyloss:[11,17],cuda:[1,11,16,17],cum:[1,3,8,9],cum_percentag:[1,3,8,9,11,17],current:[1,8],custom:[1,5,8,10],cut:[3,11,17],cutoff:[1,3,8,9],d:[4,5],d_1:3,d_n:3,dagger:[1,9],dangl:[1,9,11,12,13],data:[1,4,5,8,9,13,15,16,17],data_0:[1,15],data_1:1,data_nod:[1,13,15,16],data_node_:13,dataload:[11,17],dataset:[8,11,17],david:[4,5],ddot:4,de:[1,6,8,13],decompos:[3,9],decomposit:[0,1,8,9,11,13,17],decreas:1,dedic:8,deep:[5,11],deepcopi:1,def:[1,11,16,17],defin:[1,4,5,8,12,13,15,16,17],degre:4,del:[1,9],delet:[1,9],delete_nod:1,densiti:8,depend:[1,3],deriv:8,descent:[12,13],describ:[1,8,16],design:8,desir:[4,13,14,15,16],detail:[1,5,8,13],determin:5,develop:5,deviat:6,devic:[1,6,8,11,16,17],devot:14,df:1,diagon:[1,6,9],diagram:12,dictionari:1,did:17,differ:[1,5,8,9,10,11,12,13,14,16,17],differenti:[5,10,12],dilat:8,dim:[4,11,17],dimens:[1,3,4,8,9,11,12,16],dimension:[12,17],directli:[1,5,7,9,12,13,14,15],disconnect:[0,1,8,12],discret:0,distinct:[1,6],distinguish:15,distribut:[6,8],divers:5,document:[1,9],doe:[1,8,12,14],don:9,done:[1,3,8,12],down:8,down_bord:8,download:[5,7,17],drawn:6,drop_last:[11,17],dtype:1,due:8,duplic:8,dure:[1,11,14,15],dynam:8,e:[1,4,5,8,9,15],each:[1,3,4,6,8,9,11,12,13,14,15,16,17],earlier:12,easi:[1,5],easier:12,easili:[1,12,13,16,17],edg:[0,5,6,8,11,12,13,14,15,16,17],edge1:[1,9],edge2:[1,9],edgea:1,edgeb:1,edges_dict:1,effect:[1,13],effici:[1,5,12],einstein:13,einsum:[0,13],either:[8,9],element:[1,4,5,6,8,9,12],element_s:[11,17],els:[11,16,17],emb:[11,16],emb_a:4,emb_b:4,embed:[0,8,11,16,17],embedd:4,embedding_matric:8,empti:[0,1,8,12,13,14],enabl:[1,5,8,12,13,14],end:[1,4,9],engin:12,enhanc:5,enough:[1,8],ensur:[1,5],entail:[1,14],entangl:8,entir:1,enumer:[1,8],environ:8,epoch:[1,11,16,17],epoch_num:[11,17],eprint:5,equal:[1,8,16],equilibrium:11,equival:[1,4,8,12,17],error:8,essenti:[1,5],etc:[1,9,12,16],evalu:13,even:[1,3,5,8,12,14,16],evenli:8,everi:[1,9,12,14,17],exact:1,exactli:1,exampl:[1,4,8,9,11,13,15,16],exceed:8,excel:5,except:[6,8],exclud:[1,15],execut:9,exist:[1,8,9,13],exp:8,expand:[1,4,16],expect:[3,8],experi:[1,5,12,14],experiment:5,explain:[1,12,13,14],explan:[1,8,9],explicitli:[1,15,16],explod:8,explor:[5,12],express:9,extend:1,extra:[3,8,9],extract:[8,12],extractor:17,ey:16,eye_tensor:16,f:[11,12,13,14,15,17],facilit:[1,5,9,12],fact:[1,15,16],factor:8,fail:8,fals:[1,6,8,9,11,13,14,16,17],familiar:[5,12],fashion:13,fashionmnist:17,faster:[1,16],fastest:5,featur:[1,4,8,9,12,13,14,15,17],feature_dim:[1,16],feature_s:8,fed:17,few:5,file:5,fill:[1,6,12,13,16],finish:[12,16],finit:8,first:[1,4,5,8,9,10,12,13,14,15,17],fix:[1,12,13],flat:8,flatten:8,flavour:1,flip:17,fn_first:9,fn_next:9,folder:[5,7],follow:[1,5,7,8,9],forget:1,form:[1,8,11,12,13,15,17],former:[1,8,9],forward:[1,8,16,17],four:16,frac:[1,3,4,8,9],framework:5,free:8,friendli:5,from:[1,5,6,7,8,9,11,12,13,15,16,17],from_sid:8,from_siz:8,front:1,full:11,fulli:5,func:[1,8],functool:17,fundament:5,further:[1,5],furthermor:[1,14,16],futur:[1,14],g:[1,8,9,15],gain:5,garc:5,gaussian:8,ge:[1,3,8,9],gener:[1,14],get:[1,11,12,16,17],get_axi:[1,12],get_axis_num:[1,12],get_edg:1,git:[5,7],github:[5,7],give:[1,12,14,15],given:[1,4,8],glimps:11,go:[1,9,11,13,14,17],goal:5,goe:8,good:[1,11],gpu:[11,16],grad:[1,5,13],gradient:[1,5,8,12,13],graph:[1,12,14,16],grasp:5,greater:[1,16],greatli:5,grid:8,grid_env:8,group:9,guid:5,ha:[1,3,8,9,11,12,14,15,16,17],had:[1,9,14],hand:[1,5,8,16],happen:[1,3,8,14,17],har:5,hat:4,have:[1,3,4,8,9,11,12,13,14,15,16,17],heavi:16,height:[8,11,16],help:[1,14],henc:[1,6,9,11,12,13,14,16],here:[1,8,13],hidden:[1,15],high:[1,4,6],highli:5,hint:1,hold:[13,15],hood:14,horizont:8,how:[1,5,8,9,10,11,13,15,17],howev:[1,5,8,9,12,13,14],http:[5,7],hundread:13,hybrid:[5,10],i:[1,3,5,8,9,12,13,14,15,16],i_1:6,i_n:6,ideal:5,ident:8,identif:5,idx:12,ijb:[9,13],ijk:9,im:9,imag:[8,11,16,17],image_s:[11,16,17],immers:5,implement:[1,5,11,17],improv:11,in_1:3,in_channel:[8,17],in_dim:[5,8,11],in_env:8,in_featur:8,in_n:3,in_region:8,in_which_axi:1,includ:[1,5,8,12],incorpor:5,inde:1,independ:[1,9],index:[1,12,14],indic:[1,6,8,9,12,13,14,15],infer:[1,8,9,12,14,16],inform:[1,5,8,9,12,13,14],ingredi:[1,12],inherit:[1,8,9,15],inic:16,init:13,init_method:[1,8,11,12,13,17],initi:[0,1,8,11,12,13,16,17],inlin:8,inline_input:8,inline_mat:8,inner:1,input:[1,3,8,9,11,12,13,14,15,16,17],input_edg:[1,15,16],input_nod:16,insid:[1,5,7],insight:13,instal:[2,11,12],instanc:[1,9,14,15,16],instanti:[1,3,8,12,14,15,16],instati:12,instead:[1,8,13,14,15,16,17],integ:4,integr:5,intend:[1,8],interior:8,intermedi:[1,13,15,16],intern:8,intric:5,introduc:8,introduct:5,invari:[1,8,14,15],inverse_memori:1,involv:[1,9,14],is_attached_to:1,is_avail:[11,16,17],is_batch:[1,12],is_connected_to:1,is_dangl:[1,12],is_data:[1,15],is_leaf:[1,15],is_node1:1,is_result:[1,15],is_virtu:[1,15],isinst:[1,13],isn:[11,12],isometri:8,issu:1,item:[11,17],iter:[1,8,14],its:[1,5,6,8,9,12,14,16,17],itself:[1,8,16],itselv:8,j:[1,4,5,9],jkb:[9,13],jl:9,jo:5,joserapa98:[5,7],just:[1,5,8,12,14,15,16,17],k:[5,9],keep:[1,3,8,9,14],kei:[1,5,12],kept:[1,3,8,9],kernel:8,kernel_s:[8,17],kerstjen:5,keyword:[1,8],kib:[9,13],kind:[1,11],klm:9,know:[1,12,13,14],known:16,kwarg:[1,8],l2_reg:[11,17],l:[4,9],label:[8,11,15,17],lambda:[11,17],larger:1,largest:1,last:[1,8,9,16,17],later:16,latter:[1,8],layer:[1,5,8,11,16,17],ldot:6,lead:[1,15],leaf:[1,9,15,16],leaf_nod:[1,15],learn:[1,5,9,11,12,13,14,15,16,17],learn_rat:[11,17],learnabl:13,least:[1,9,11,14,17],leav:[8,14],left1:13,left2:13,left:[1,5,8,9,12,13,14,15,16,17],left_bord:8,left_down_corn:8,left_nod:8,left_up_corn:8,leg:1,len:[9,16],length:[1,8],let:[11,12,13,14,16,17],level:[1,4,12],leverag:12,lfloor:4,li:[1,5,12],librari:[5,12,17],like:[0,1,5,8,11,12,13,14,15,16,17],limit:6,line:[5,8,17],linear:[5,16],link:1,list:[1,3,6,8,9,12,13,16],load:[11,16],load_state_dict:16,loader:[11,17],local:[4,8],locat:[1,8],log_norm:8,logarithm:8,logit:[11,17],longer:[1,14],loop:[11,16],lose:11,loss:17,loss_fun:[11,17],lot:[1,11],low:[1,4,6,8],lower:[1,3,6,8,9],lr:[11,17],lvert:4,m:[1,3,5,7,9],machin:[5,11],made:[1,9],mai:[1,3,5,8],main:[1,12,13,14,16],mainli:1,maintain:1,make:[1,12,13,14,15],make_tensor:[1,8],manag:[1,16],mandatori:[1,9],mani:[1,8,11,12,14,17],manipul:12,manual:[1,8,16],manual_se:[11,17],map:4,margin:8,marginalize_output:8,master:[5,7],mat:3,mat_to_mpo:0,match:[1,8,9],matric:[8,9,13,16],matrix:[1,3,4,5,8,9,11,12,14,16],mats_env:8,max:[11,17],max_bond:8,maximum:[4,8],maxpool2d:17,mayb:9,mb:[11,17],mean:[1,6,8],megabyt:[11,17],mehtod:12,member:1,memori:[1,5,10,11,12,15,16,17],mention:[8,16],method:[1,8,12,13,14,15,16],mi:8,middl:8,middle_sit:8,might:[1,8,14,15,16],mile:4,mimic:16,minuend:9,misc:5,miscellan:[11,17],mit:5,mnist:11,mod:4,mode:[1,8,9,11,15,16,17],model:[0,1,5,10,12,13,14,15],modif:1,modifi:[1,8,13],modul:[1,5,7,8,11,12,13,16,17],modulelist:17,monomi:4,monturiol:5,mooth:5,more:[1,3,5,8,9,12,13,14,15],most:[1,5,12,15],move:[1,8,12],move_nam:1,move_to_network:[1,9],movec:9,mp:[0,3,5,11,12,13,14,15,16,17],mpo:[0,3,16],mps_data:8,mps_layer:8,mpsdata:[0,3],mpslayer:[0,5,11,16],much:[1,14,15,16,17],mul:[0,13],multi:12,multipl:[8,12,14],multipli:[8,9],must:[1,3,9,12,16],mutual:8,my_model:5,my_nod:[1,14],my_paramnod:1,my_stack:1,mybatch:1,n:[1,4,5,6,8,11,17],n_:[1,4],n_batch:[3,8],n_col:8,n_epoch:16,n_featur:[5,8,11,16],n_param:[11,17],n_row:8,name:[1,5,6,9,11,12,13,14,16,17],name_:1,necessari:[1,8,9,11,12,16,17],need:[1,8,9,11,12,13,14,16],neighbour:[1,8,13],nelement:[11,17],net2:1,net:[1,5,9,12,13,14,15],network:[0,5,6,8,9,10,14,15,16],neural:[5,8,10,11],never:[1,9,15],nevertheless:5,new_edg:[1,9],new_edgea:1,new_edgeb:1,new_mp:16,new_nodea:[1,9],new_nodeb:[1,9],new_tensor:14,next:[1,8,9,12,13,14,15],nn:[1,5,8,11,12,13,16,17],no_grad:[11,17],node1:[1,5,9,12,13,14,15],node1_ax:[1,9],node1_list:1,node1_lists_dict:1,node2:[1,5,9,12,13,14,15],node2_ax:[1,9],node3:[13,14],node:[0,5,6,8,10,11,14,16,17],node_0:1,node_1:1,node_:[12,13,14,15],node_left:[1,9],node_n:1,node_ref:1,node_right:[1,9],nodea:[1,9],nodeb:[1,9],nodec:[1,9],nodes_list:9,nodes_nam:1,nodesa:9,nodesb:9,nodesc:9,non:[1,8,9,13,16],none:[1,3,5,6,8,9,13,16],norm:[1,8],normal:[1,6,8],notabl:5,notat:12,note:[1,5,9,12,13,16],noth:[1,12,14],now:[1,12,13,14,16,17],nu_batch:8,num:1,num_batch:[11,17],num_batch_edg:[1,15,16],num_epoch:[11,17],num_epochs_canon:11,num_test:[11,17],num_train:[11,17],number:[1,3,8,9,11,13,16,17],numel:1,nummber:1,o:5,obc:[3,8],object:[1,12,17],obtain:[8,11],oc:8,occur:[1,14],off:[11,14,17],offer:5,often:[1,14],onc:[1,5,9,14],one:[1,3,8,9,11,12,13,14,15,16,17],ones:[0,1,4,8,12,13,15,17],ones_lik:[11,17],onli:[1,5,8,9,12,13,14,15,16],open:8,oper:[0,1,8,12,15,16],operand:[1,12,13],opposit:8,opt_einsum:[5,9,13],optim:[1,5,17],option:[1,3,4,5,6,8,9,12,16],order:[1,3,8,9,12,13,14,15,16],orient:8,origin:[1,4,8,9,11,15,17],orthogon:8,ot:8,other:[1,8,9,12,13,14,15,16,17],other_left:12,other_nod:14,otherwis:[1,8,9,17],otim:4,our:[11,12,13,14,17],out:[1,8,9,16],out_1:3,out_channel:[8,17],out_dim:[5,8,11],out_env:8,out_featur:8,out_n:3,out_nod:8,out_posit:8,out_region:8,output:[1,3,4,8,9,11,16],output_nod:16,outsid:[5,7],over:[1,13],overcom:1,overhead:14,overload:1,overrid:[1,8,16],override_edg:1,override_nod:1,overriden:[1,13,16],own:[1,8,9,14,15],ownership:1,p:[1,5,11,17],packag:[5,7],pad:[8,17],pair:[1,8,17],pairwis:8,paper:[4,5,8,17],parallel:[1,8],param:[1,9,11,17],param_nod:[1,5,6,13,15],paramedg:1,paramet:[1,3,4,6,8,9,11,13,16,17],parameter:[1,8,13],parametr:[1,8,11,13,16,17],paramnod:[0,6,8,9,14,15,16],paramnode1:13,paramnode2:13,paramnode3:13,paramnodea:1,paramnodeb:1,paramstacknod:[0,9,14,15],pareja2023tensorkrowch:5,pareja:5,parent:[1,16],parenthesi:1,part:[1,8,13,15,16],partial:[8,17],partial_dens:8,particular:[1,16],pass:[8,11,16],patch:[8,16],pattern:17,pave:11,pbc:8,pep:[0,5,16],perform:[1,5,8,9,11,12,13,14,16,17],period:8,permut:[0,1,13,14],permute_:[0,1,13],phase:9,phi:4,phys_dim:8,physic:[3,8,12],pi:4,piec:[12,16],pip:[5,7,11,12],pipelin:[5,11],pixel:[8,11,16,17],place:[1,8,9,13,14,16],plai:15,pleas:5,plu:8,plug:8,point:[1,11],poli:[0,16],pool:17,posit:[8,16],possibl:[1,8,11,12,13,14],potenti:5,power:[4,11],poza:5,practic:[1,5],practition:11,precis:4,pred:[11,17],predict:[11,17],present:17,previou:[1,8,13,14,15,16,17],previous:13,primari:5,principl:[1,15],print:[1,8,9,11,17],probabl:11,problem:[1,14],process:[1,5,11],product:[5,8,9,11,12,14,16],profici:5,project:8,properli:16,properti:[1,8,9,12],proport:[1,3,8,9],prove:8,provid:[1,3,5,8,9,12],prune:17,pt:16,purpos:[1,12,14,15],put:[1,8,11,17],pytest:[5,7],python:[5,7,11],pytorch:[1,5,9,11,12,13,14,16,17],q:9,qr:[0,1,8,13],qr_:[0,1,8],quantiti:[1,3,8,9],quantum:12,qubit:8,quit:[5,17],r:[5,9],r_1:9,r_2:9,rais:1,ram:5,rand:[0,1,4,8],randint:4,randn:[0,1,4,5,8,9,12,13,14,15,16],randn_ey:[8,11,17],random:[8,9,13],random_ey:16,rang:[1,5,8,9,11,12,13,14,15,16,17],rangl:4,rank:[1,3,4,8,9,13],rapid:5,rather:[1,8,12,14,15],re:[1,9,11,17],reach:11,readi:16,realli:[1,15],reattach:1,reattach_edg:1,recal:[1,16],recogn:13,recommend:[1,5,8],reconnect:[1,13],recov:[1,3,8],reduc:[1,8,11,17],redund:[1,9],refer:[1,2,9,12,13,14,15,16],referenc:1,regard:[1,9,13],regio:8,relat:12,relev:[1,12],reli:5,relu:[5,17],remain:8,remov:[1,9],renorm:8,repeat:[1,9],repeatedli:14,replac:1,repositori:[5,7],repres:[8,12],reproduc:[1,14],requir:[1,8,9,15],requires_grad:1,res1:13,res2:13,reserv:[1,9,14],reset:[1,8,9,14,15,16],reset_tensor_address:1,reshap:[3,9,14],resiz:[11,17],respect:[1,3,8,9,13,14],rest:[1,13],restrict:[1,9,15],result2:9,result:[1,3,5,8,9,13,14,15,16],resultant_nod:[1,15],retriev:[1,12,14,15],reus:8,rez:5,rfloor:4,rid:1,right1:13,right2:13,right:[1,5,8,9,12,13,14,15,16,17],right_bord:8,right_down_corn:8,right_nod:8,right_up_corn:8,rightmost:8,ring:13,role:[1,8,15,16],row:8,rowch:5,rq:[0,1],rq_:[0,1,8],run:[5,7,8,11,17],running_acc:11,running_test_acc:[11,17],running_train_acc:[11,17],running_train_loss:[11,17],s:[1,4,5,6,8,9,11,12,13,14,15,16,17],s_1:4,s_i:[1,3,8,9],s_j:4,s_n:4,sai:[1,14],same:[1,8,9,12,13,14,17],sampler:[11,17],satisfi:[1,3,8,9],save:[1,5,10,13,16],scalar:8,scale:8,scenario:5,schwab:4,score:[11,17],second:[1,8,9,14],secondli:16,section:[12,13,15,17],see:[1,5,8,9,15,17],seen:15,select:[1,9,12,13,16],self:[1,8,16,17],send:[11,16,17],sens:[11,15],sento:16,separ:[8,9],sepcifi:1,sequenc:[1,3,6,8,9,13,16],sequenti:[5,16],serv:11,set:[1,8,9,14,15,16,17],set_data_nod:[1,8,15,16],set_param:[1,8],set_tensor:[1,8,14],set_tensor_from:[1,14,15,16],sever:[1,9],shall:[1,16],shape:[1,3,4,5,6,8,9,11,12,13,14,15,16],shape_all_nodes_layer_1:8,shape_all_nodes_layer_n:8,shape_node_1_layer_1:8,shape_node_1_layer_n:8,shape_node_i1_layer_1:8,shape_node_in_layer_n:8,share:[1,8,9,12,14,15],share_tensor:[1,8],should:[1,3,4,6,8,9,12,13,14,15,16],show:12,side:[1,8,9,17],similar:[1,8,9,12,13,15,16],simpl:[5,12,13],simpli:[1,12,14,16],simplifi:[1,5],simul:12,sin:4,sinc:[1,8,9,13,14,15,16,17],singl:[1,8,9,13,16],singular:[1,3,8,9,11,13,17],site:[8,12],sites_per_lay:8,situat:[1,15],size:[1,4,6,8,9,12,13,15,16,17],skip:1,skipp:14,slice:[1,9,14,15],slide:1,slower:[1,14],small:13,smaller:1,smooth:5,snake:[8,17],so:[1,8,9,14,16],some:[1,3,8,9,11,12,13,14,15,16],someth:12,sometim:1,sort:[1,8,12,14,15],sourc:[1,3,4,6,8,9],space:[1,6,11,16,17],special:[1,13,15],specif:[1,5,15],specifi:[1,3,8,9,12,13,15],split:[0,1,3,8,12,13],split_0:[1,9],split_1:[1,9],split_:[0,1,13],split_ip:[1,9],split_ip_0:[1,9],split_ip_1:[1,9],sqrt:4,squar:9,stack:[0,1,4,8,11,13,14,15,16],stack_0:1,stack_1:1,stack_data:[1,9,16],stack_data_memori:[1,15],stack_data_nod:13,stack_input:16,stack_nod:[1,9,13,14,15],stack_result:[13,16],stackabl:9,stacked_einsum:[0,13],stackedg:[0,9,13],stacknod:[0,9,13,15],stai:1,stand:4,standard:6,start:[1,8,12,14,16,17],state:[1,5,8,11,12,14,16],state_dict:16,staticmethod:17,std:[1,6,8,11,16,17],step:[1,5,10,17],stick:1,still:[1,8,9,13],store:[1,8,9,12,15,16],stoudenmir:4,str:[1,6,9],straightforward:5,strength:5,stride:[8,17],string:[1,8,9,15],structur:[1,5,8,12,16,17],sub:[0,13],subclass:[1,5,10,12,15,17],subsequ:[1,14],subsetrandomsampl:[11,17],substitut:1,subsystem:8,subtract:9,subtrahend:9,successor:[0,9],suffix:1,suitabl:5,sum:[1,3,8,9,11,13,17],sum_:[1,3,8,9],summat:13,support:5,sv:[1,9],svd:[0,1,3,8,11],svd_:[0,1,8,13],svdr:[0,1,8],svdr_:[0,1,8],t:[8,9,11,12,15,16],t_:[6,9],tailor:5,take:[1,8,11,14,15,16],taken:1,task:[1,8,15],temporari:[1,15],tensor:[0,3,4,5,6,8,10,15,16],tensor_address:[1,8,14,15],tensorkrowch:[1,3,4,6,7,8,9,10,13,15,16,17],tensornetwork:[1,5,6,9,10,13,15,17],termin:[5,7],test:[5,7,11,17],test_set:[11,17],textrm:8,th:8,than:[1,3,8,9,12,16],thank:12,thei:[1,5,6,7,8,9,12,13,15],them:[1,8,9,11,12,13,14,16,17],themselv:[8,9],therefor:[1,5,7,8,12,16],thi:[1,3,4,5,8,9,11,12,13,14,15,16,17],thing:[1,9,11,15,17],think:14,third:8,those:[1,14,15,16],though:[1,8,9,13,16],thought:1,three:[1,9],through:[1,5,8,9,11,16,17],thu:[1,3,5,8,9,12,16],time:[1,3,4,5,8,9,10,12,13,16],titl:5,tk:[1,4,5,8,9,11,12,13,14,15,16,17],tn:16,togeth:[1,5,7,14,15],tool:[5,12,13],top:[1,5,8,11,13,17],torch:[1,3,4,5,6,8,9,11,12,13,14,15,16,17],torchvis:[11,17],total:[1,3,8,9,11,17],total_num:[11,17],totensor:[11,17],tprod:[0,13],trace:[1,8,9,11,15,16,17],trace_sit:8,track:[1,14],train:[1,5,12,13,14,16,17],train_set:[11,17],trainabl:[1,8,12,15,16,17],transform:[4,8,11,13,17],transit:8,translation:[1,8,14,15],transpos:[11,16,17],treat:1,tree:[0,16],tri:[1,9],triangular:9,trick:[14,17],tupl:[1,6,8,9],turn:[1,8,14],tutori:[1,2,11,12,13,14,15,16,17],two:[1,8,9,12,13,14],two_0:9,type:[1,3,4,5,6,8,9,10,12,13,14],typeerror:1,u:[1,6,9],ump:0,umpo:0,umpslay:0,unbind:[0,1,13,14,16],unbind_0:[1,9],unbind_result:13,unbinded_nod:[13,14],unbound:[1,9],under:[5,14],underli:14,underscor:[1,9],understand:[5,12],undesir:[1,8,15],unfold:8,uniform:[1,6,8,14,15,16],uniform_memori:16,uniform_nod:[14,15],uninform:11,uniqu:[8,14],unit:[0,8],unitari:[8,9],univoc:8,unix:[5,7],unless:1,unset:1,unset_data_nod:[1,8],unset_tensor:1,unsqueez:17,until:[1,8,9,11],up:[1,8,9,12],up_bord:8,updat:[11,17],upep:0,upon:8,upper:[6,8,9],ur_1sr_2v:9,us:[1,3,4,5,6,8,9,11,12,13,14,15,16,17],usag:1,useless:11,user:[1,5],usual:[1,8,9,15,16],usv:9,util:[11,17],utre:0,v:[1,5,7,9],valid:1,valu:[1,3,4,8,9,11,13,16,17],valueerror:1,vanilla:[12,13,14],variabl:4,variant:[13,16],variat:1,variou:[5,8],vdot:4,vec:3,vec_to_mp:0,vector:[3,4,8,11,16,17],veri:[1,11,12,14,16],verifi:[1,9],versa:8,versatil:5,version:[1,8,9,13],vertic:[1,8],via:[1,3,8,9,12,13,14,15],vice:8,view:[11,16,17],virtual:[1,9,14,15,16],virtual_nod:[1,15],virtual_result:[1,15],virtual_result_stack:[1,9,15],virtual_uniform:[1,8,15,16],virtual_uniform_mp:1,visit:1,wa:[1,8,16,17],wai:[1,8,11,12,16],want:[1,8,12,13,14,16],we:[1,11,12,13,14,15,16,17],weight_decai:[11,17],well:[1,8,9],were:[1,8,13,15,16],what:[1,12,14,16],when:[1,8,9,11,12,13,14,15,16],whenev:[1,9],where:[1,4,6,8,9,11,12,13,14,16],whether:[1,6,8,12,14],which:[1,4,6,8,9,11,12,13,14,15,16],whilst:8,who:5,whole:[1,8,11,13,14,15,16],whose:[1,8,9,14,15],why:14,wide:[5,16],width:[8,11,16],wise:[1,9],wish:16,without:[1,6,8,9,11],won:[8,16],word:[1,12],work:[5,13,17],workflow:[1,16],worri:[1,14],would:[1,8,9,12,14,17],wouldn:15,wow:17,wrap:[1,12],written:[5,7],x:[4,5,8,11,16,17],x_1:4,x_j:4,x_n:4,y1:17,y2:17,y3:17,y4:17,y:17,yet:[12,16],you:[5,7,11,12,13,14,15,16],your:[5,7,11,12,16],your_nod:14,yourself:5,zero:[0,1,8,11,16,17],zero_grad:[11,17],zeros_lik:14},titles:["API Reference","Components","<no title>","Decompositions","Embeddings","TensorKrowch documentation","Initializers","Installation","Models","Operations","Tutorials","First Steps with TensorKrowch","Creating a Tensor Network in TensorKrowch","Contracting and Differentiating the Tensor Network","How to save Memory and Time with TensorKrowch (ADVANCED)","The different Types of Nodes (ADVANCED)","How to subclass TensorNetwork to build Custom Models","Creating a Hybrid Neural-Tensor Network Model"],titleterms:{"1":[11,12,13,14,15,16],"2":[11,12,13,14,15,16],"3":[11,12,13,14,16],"4":11,"5":11,"6":11,"7":11,"class":9,"function":11,"import":11,The:[15,16],abstractnod:1,add:9,add_on:4,advanc:[14,15],api:0,ar:14,axi:1,basi:4,between:13,build:[12,16],choos:11,cite:5,compon:[1,12,16],connect:9,connect_stack:9,content:2,contract:[9,13],contract_:9,contract_between:9,contract_between_:9,contract_edg:9,convmp:8,convmpslay:8,convpep:8,convtre:8,convump:8,convumpslay:8,convupep:8,convutre:8,copi:6,creat:[12,17],custom:16,data:11,decomposit:3,differ:15,differenti:13,disconnect:9,discret:4,distinguish:13,document:5,download:11,edg:[1,9],einsum:9,embed:4,empti:6,everyth:16,exampl:5,faster:14,first:[11,16],how:[12,14,16],hybrid:17,hyperparamet:11,initi:6,instal:[5,7],instanti:11,introduct:[11,12,13,14,15,16],librari:11,licens:5,like:9,loss:11,manag:14,mat_to_mpo:3,matrix:13,memori:14,mode:14,model:[8,11,16,17],mp:8,mpo:8,mpsdata:8,mpslayer:8,mul:9,name:15,network:[1,11,12,13,17],neural:17,node:[1,9,12,13,15],ones:6,oper:[9,13,14],optim:11,our:16,paramnod:[1,13],paramstacknod:1,pep:8,permut:9,permute_:9,poli:4,product:13,prune:11,put:16,qr:9,qr_:9,rand:6,randn:6,refer:0,requir:5,reserv:15,rq:9,rq_:9,run:14,save:14,set:11,setup:[11,12],skip:14,split:9,split_:9,stack:9,stacked_einsum:9,stackedg:1,stacknod:1,start:11,state:13,step:[11,12,13,14,15,16],store:14,sub:9,subclass:16,successor:1,svd:9,svd_:9,svdr:9,svdr_:9,tensor:[1,9,11,12,13,14,17],tensorkrowch:[5,11,12,14],tensornetwork:[12,14,16],time:14,togeth:16,tprod:9,train:11,tree:8,tutori:[5,10],type:15,ump:8,umpo:8,umpslay:8,unbind:9,unit:4,upep:8,utre:8,vec_to_mp:3,zero:6}}) \ No newline at end of file +Search.setIndex({docnames:["api","components","contents","decompositions","embeddings","index","initializers","installation","models","operations","tutorials","tutorials/0_first_steps","tutorials/1_creating_tensor_network","tutorials/2_contracting_tensor_network","tutorials/3_memory_management","tutorials/4_types_of_nodes","tutorials/5_subclass_tensor_network","tutorials/6_mix_with_pytorch"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":5,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.viewcode":1,sphinx:56},filenames:["api.rst","components.rst","contents.rst","decompositions.rst","embeddings.rst","index.rst","initializers.rst","installation.rst","models.rst","operations.rst","tutorials.rst","tutorials/0_first_steps.rst","tutorials/1_creating_tensor_network.rst","tutorials/2_contracting_tensor_network.rst","tutorials/3_memory_management.rst","tutorials/4_types_of_nodes.rst","tutorials/5_subclass_tensor_network.rst","tutorials/6_mix_with_pytorch.rst"],objects:{"tensorkrowch.AbstractNode":[[1,1,1,"","axes"],[1,1,1,"","axes_names"],[1,2,1,"","contract_between"],[1,2,1,"","contract_between_"],[1,1,1,"","device"],[1,2,1,"","disconnect"],[1,1,1,"","dtype"],[1,1,1,"","edges"],[1,2,1,"","get_axis"],[1,2,1,"","get_axis_num"],[1,2,1,"","get_edge"],[1,2,1,"","in_which_axis"],[1,2,1,"","is_connected_to"],[1,2,1,"","is_data"],[1,2,1,"","is_leaf"],[1,2,1,"","is_node1"],[1,2,1,"","is_resultant"],[1,2,1,"","is_virtual"],[1,2,1,"","make_tensor"],[1,2,1,"","mean"],[1,2,1,"","move_to_network"],[1,1,1,"","name"],[1,2,1,"","neighbours"],[1,1,1,"","network"],[1,2,1,"","node_ref"],[1,2,1,"","norm"],[1,2,1,"","numel"],[1,2,1,"","permute"],[1,2,1,"","permute_"],[1,1,1,"","rank"],[1,2,1,"","reattach_edges"],[1,2,1,"","renormalize"],[1,2,1,"","reset_tensor_address"],[1,2,1,"","set_tensor"],[1,2,1,"","set_tensor_from"],[1,1,1,"","shape"],[1,2,1,"","size"],[1,2,1,"","split"],[1,2,1,"","split_"],[1,2,1,"","std"],[1,1,1,"","successors"],[1,2,1,"","sum"],[1,1,1,"","tensor"],[1,2,1,"","tensor_address"],[1,2,1,"","unset_tensor"]],"tensorkrowch.Axis":[[1,2,1,"","is_batch"],[1,2,1,"","is_node1"],[1,1,1,"","name"],[1,1,1,"","node"],[1,1,1,"","num"]],"tensorkrowch.Edge":[[1,1,1,"","axes"],[1,1,1,"","axis1"],[1,1,1,"","axis2"],[1,2,1,"","change_size"],[1,2,1,"","connect"],[1,2,1,"","contract"],[1,2,1,"","contract_"],[1,2,1,"","copy"],[1,2,1,"","disconnect"],[1,2,1,"","is_attached_to"],[1,2,1,"","is_batch"],[1,2,1,"","is_dangling"],[1,1,1,"","name"],[1,1,1,"","node1"],[1,1,1,"","node2"],[1,1,1,"","nodes"],[1,2,1,"","qr"],[1,2,1,"","qr_"],[1,2,1,"","rq"],[1,2,1,"","rq_"],[1,2,1,"","size"],[1,2,1,"","svd"],[1,2,1,"","svd_"],[1,2,1,"","svdr"],[1,2,1,"","svdr_"]],"tensorkrowch.Node":[[1,2,1,"","change_type"],[1,2,1,"","copy"],[1,2,1,"","parameterize"]],"tensorkrowch.ParamNode":[[1,2,1,"","change_type"],[1,2,1,"","copy"],[1,1,1,"","grad"],[1,2,1,"","parameterize"]],"tensorkrowch.ParamStackNode":[[1,1,1,"","edges_dict"],[1,1,1,"","node1_lists_dict"],[1,2,1,"","reconnect"],[1,2,1,"","unbind"]],"tensorkrowch.StackEdge":[[1,2,1,"","connect"],[1,1,1,"","edges"],[1,1,1,"","node1_list"]],"tensorkrowch.StackNode":[[1,1,1,"","edges_dict"],[1,1,1,"","node1_lists_dict"],[1,2,1,"","reconnect"],[1,2,1,"","unbind"]],"tensorkrowch.TensorNetwork":[[1,2,1,"","add_data"],[1,1,1,"","auto_stack"],[1,1,1,"","auto_unbind"],[1,2,1,"","contract"],[1,2,1,"","copy"],[1,1,1,"","data_nodes"],[1,2,1,"","delete_node"],[1,1,1,"","edges"],[1,2,1,"","forward"],[1,1,1,"","leaf_nodes"],[1,1,1,"","nodes"],[1,1,1,"","nodes_names"],[1,2,1,"","parameterize"],[1,2,1,"","reset"],[1,1,1,"","resultant_nodes"],[1,2,1,"","set_data_nodes"],[1,2,1,"","trace"],[1,2,1,"","unset_data_nodes"],[1,1,1,"","virtual_nodes"]],"tensorkrowch.decompositions":[[3,3,1,"","mat_to_mpo"],[3,3,1,"","vec_to_mps"]],"tensorkrowch.embeddings":[[4,3,1,"","add_ones"],[4,3,1,"","basis"],[4,3,1,"","discretize"],[4,3,1,"","poly"],[4,3,1,"","unit"]],"tensorkrowch.models":[[8,0,1,"","ConvMPS"],[8,0,1,"","ConvMPSLayer"],[8,0,1,"","ConvPEPS"],[8,0,1,"","ConvTree"],[8,0,1,"","ConvUMPS"],[8,0,1,"","ConvUMPSLayer"],[8,0,1,"","ConvUPEPS"],[8,0,1,"","ConvUTree"],[8,0,1,"","MPO"],[8,0,1,"","MPS"],[8,0,1,"","MPSData"],[8,0,1,"","MPSLayer"],[8,0,1,"","PEPS"],[8,0,1,"","Tree"],[8,0,1,"","UMPO"],[8,0,1,"","UMPS"],[8,0,1,"","UMPSLayer"],[8,0,1,"","UPEPS"],[8,0,1,"","UTree"]],"tensorkrowch.models.ConvMPS":[[8,2,1,"","copy"],[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvMPSLayer":[[8,2,1,"","copy"],[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","out_channels"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvPEPS":[[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvTree":[[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvUMPS":[[8,2,1,"","copy"],[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvUMPSLayer":[[8,2,1,"","copy"],[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","out_channels"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvUPEPS":[[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.ConvUTree":[[8,1,1,"","dilation"],[8,2,1,"","forward"],[8,1,1,"","in_channels"],[8,1,1,"","kernel_size"],[8,1,1,"","padding"],[8,1,1,"","stride"]],"tensorkrowch.models.MPO":[[8,1,1,"","bond_dim"],[8,1,1,"","boundary"],[8,2,1,"","canonicalize"],[8,2,1,"","contract"],[8,2,1,"","copy"],[8,1,1,"","in_dim"],[8,2,1,"","initialize"],[8,1,1,"","left_node"],[8,1,1,"","mats_env"],[8,1,1,"","n_batches"],[8,1,1,"","n_features"],[8,1,1,"","out_dim"],[8,2,1,"","parameterize"],[8,1,1,"","right_node"],[8,2,1,"","set_data_nodes"]],"tensorkrowch.models.MPS":[[8,1,1,"","bond_dim"],[8,1,1,"","boundary"],[8,2,1,"","canonicalize"],[8,2,1,"","canonicalize_univocal"],[8,2,1,"","contract"],[8,2,1,"","copy"],[8,2,1,"","entropy"],[8,1,1,"","in_env"],[8,1,1,"","in_features"],[8,1,1,"","in_regions"],[8,2,1,"","initialize"],[8,1,1,"","left_node"],[8,1,1,"","mats_env"],[8,1,1,"","n_batches"],[8,1,1,"","n_features"],[8,2,1,"","norm"],[8,1,1,"","out_env"],[8,1,1,"","out_features"],[8,1,1,"","out_regions"],[8,2,1,"","parameterize"],[8,2,1,"","partial_density"],[8,1,1,"","phys_dim"],[8,1,1,"","right_node"],[8,2,1,"","set_data_nodes"]],"tensorkrowch.models.MPSData":[[8,2,1,"","add_data"],[8,1,1,"","bond_dim"],[8,1,1,"","boundary"],[8,2,1,"","initialize"],[8,1,1,"","left_node"],[8,1,1,"","mats_env"],[8,1,1,"","n_batches"],[8,1,1,"","n_features"],[8,1,1,"","phys_dim"],[8,1,1,"","right_node"]],"tensorkrowch.models.MPSLayer":[[8,2,1,"","copy"],[8,1,1,"","in_dim"],[8,2,1,"","initialize"],[8,1,1,"","out_dim"],[8,1,1,"","out_node"],[8,1,1,"","out_position"]],"tensorkrowch.models.PEPS":[[8,1,1,"","bond_dim"],[8,1,1,"","boundary"],[8,2,1,"","contract"],[8,1,1,"","in_dim"],[8,2,1,"","initialize"],[8,1,1,"","n_batches"],[8,1,1,"","n_cols"],[8,1,1,"","n_rows"],[8,2,1,"","set_data_nodes"]],"tensorkrowch.models.Tree":[[8,1,1,"","bond_dim"],[8,2,1,"","canonicalize"],[8,2,1,"","contract"],[8,2,1,"","initialize"],[8,1,1,"","n_batches"],[8,2,1,"","set_data_nodes"],[8,1,1,"","sites_per_layer"]],"tensorkrowch.models.UMPO":[[8,2,1,"","copy"],[8,2,1,"","initialize"],[8,2,1,"","parameterize"]],"tensorkrowch.models.UMPS":[[8,2,1,"","copy"],[8,2,1,"","initialize"],[8,2,1,"","parameterize"]],"tensorkrowch.models.UMPSLayer":[[8,2,1,"","copy"],[8,1,1,"","in_dim"],[8,2,1,"","initialize"],[8,1,1,"","out_dim"],[8,1,1,"","out_node"],[8,1,1,"","out_position"],[8,2,1,"","parameterize"]],"tensorkrowch.models.UPEPS":[[8,1,1,"","bond_dim"],[8,2,1,"","contract"],[8,1,1,"","in_dim"],[8,2,1,"","initialize"],[8,1,1,"","n_batches"],[8,1,1,"","n_cols"],[8,1,1,"","n_rows"],[8,2,1,"","set_data_nodes"]],"tensorkrowch.models.UTree":[[8,1,1,"","bond_dim"],[8,2,1,"","contract"],[8,2,1,"","initialize"],[8,1,1,"","n_batches"],[8,2,1,"","set_data_nodes"],[8,1,1,"","sites_per_layer"]],tensorkrowch:[[1,0,1,"","AbstractNode"],[1,0,1,"","Axis"],[1,0,1,"","Edge"],[1,0,1,"","Node"],[9,0,1,"","Operation"],[1,0,1,"","ParamNode"],[1,0,1,"","ParamStackNode"],[1,0,1,"","StackEdge"],[1,0,1,"","StackNode"],[1,0,1,"","Successor"],[1,0,1,"","TensorNetwork"],[9,3,1,"","add"],[9,3,1,"","connect"],[9,3,1,"","connect_stack"],[9,3,1,"","contract"],[9,3,1,"","contract_"],[9,3,1,"","contract_between"],[9,3,1,"","contract_between_"],[9,3,1,"","contract_edges"],[6,3,1,"","copy"],[9,3,1,"","disconnect"],[9,3,1,"","div"],[9,3,1,"","einsum"],[6,3,1,"","empty"],[9,3,1,"","mul"],[6,3,1,"","ones"],[9,3,1,"","permute"],[9,3,1,"","permute_"],[9,3,1,"","qr"],[9,3,1,"","qr_"],[6,3,1,"","rand"],[6,3,1,"","randn"],[9,3,1,"","renormalize"],[9,3,1,"","rq"],[9,3,1,"","rq_"],[9,3,1,"","split"],[9,3,1,"","split_"],[9,3,1,"","stack"],[9,3,1,"","stacked_einsum"],[9,3,1,"","sub"],[9,3,1,"","svd"],[9,3,1,"","svd_"],[9,3,1,"","svdr"],[9,3,1,"","svdr_"],[9,3,1,"","tprod"],[9,3,1,"","unbind"],[6,3,1,"","zeros"]]},objnames:{"0":["py","class","Python class"],"1":["py","property","Python property"],"2":["py","method","Python method"],"3":["py","function","Python function"]},objtypes:{"0":"py:class","1":"py:property","2":"py:method","3":"py:function"},terms:{"0":[1,3,4,5,8,9,11,13,15,16,17],"00":4,"0000":4,"0000e":4,"0086":1,"0101":1,"0188":1,"0234":1,"0371":1,"0412":1,"0440":1,"0445":1,"0465":1,"0501":1,"0521":1,"0633":1,"0639":11,"0704":1,"0728":11,"0743":1,"0750":11,"08":4,"0806":1,"0820":11,"08595":5,"09":8,"0913":11,"0983":11,"0998":11,"1":[1,3,4,5,8,9,17],"10":[1,4,5,8,9,11,13,16,17],"100":[1,4,5,8,9,12,13,14,15],"10000":[11,17],"1024":[11,17],"1091":1,"1123":11,"1211":1,"1308":11,"1371":1,"14":17,"1496":1,"15":[1,9,13],"1573":11,"161204":11,"1654":1,"1878":17,"1955":11,"1990":11,"1998":1,"1e":[8,11,16,17],"2":[1,4,8,9,17],"20":[1,8,9,17],"2052":17,"2091":1,"2095":1,"2111":1,"2203":17,"2254":1,"2306":5,"2316":1,"2357":17,"236220":11,"2380":1,"2390":1,"2449":1,"2461":1,"2477":1,"2503":1,"2517":1,"2618":17,"2730":1,"2775":1,"2799":1,"28":[11,17],"2808":1,"2840":17,"2856":1,"2866":1,"2898":1,"2d":8,"3":[1,4,5,8,9,15,17],"30":17,"3083":11,"3088":1,"3139":1,"3149":17,"32":[8,11],"3222":1,"3340":1,"3370":1,"3371":1,"3381":1,"3393":1,"3427":1,"3489":1,"3508":1,"3513":1,"3585":1,"3711e":4,"3714":17,"3760":1,"3784":1,"3821":1,"4":[1,4,8,9,13,15,17],"40":17,"4005":1,"4181":1,"4184":1,"4216":1,"4383":1,"4385":1,"4402":1,"4431":1,"4461":1,"4572":1,"4588":1,"4731":1,"4761":1,"4974":1,"499320":17,"4f":[11,17],"5":[1,4,5,8,9,12,13,14,15,17],"50":17,"500":[5,11,16,17],"5000":4,"5021":1,"5029":1,"5069":1,"5161":1,"5224":17,"5401":1,"5406":1,"553186":17,"5567":1,"5570":1,"5676":11,"5731":1,"5760":1,"5797":1,"5920":1,"6":[1,4,8,17],"60":17,"60000":[11,17],"6023":1,"6149":11,"6225":1,"6227":1,"6295":1,"6356":1,"6399":1,"6492":1,"6495":1,"6524":1,"6545":1,"6551":1,"6811":1,"6925":1,"6982":1,"7":[1,5,8,9,13,14,17],"70":17,"75":4,"7500":4,"7592":1,"7688":1,"7752":1,"7812":1,"7997":1,"8":[5,8,11],"80":17,"8090":1,"8147":1,"8227":1,"8361":1,"8387":1,"8441":1,"8442":1,"8502":17,"8627":17,"8633":1,"8649":1,"8795":17,"8815":1,"8820":11,"8848":17,"8851":17,"8859":1,"8901":17,"8911":1,"8915":17,"8948":17,"8968":17,"8984":17,"9":[5,8,11,16,17],"90":17,"9006":1,"9009":17,"9011":[11,17],"9026":17,"9048":17,"9053":11,"9125":17,"9145":1,"9174":17,"9231":17,"9265":1,"9284":17,"9320":1,"9371":11,"9396":11,"9400":11,"9432":1,"9509":11,"9526":11,"9551":1,"9561":1,"9585":11,"9600":11,"9618":1,"9621":11,"9625":11,"9668":11,"9677":11,"9696":11,"9700":11,"9721":11,"9729":11,"9731":11,"9734":11,"9736":11,"9738":11,"9743":11,"9768":11,"9775":11,"9793":11,"98":17,"9844":1,"99":11,"9925":1,"9942":1,"9957":1,"abstract":1,"boolean":[1,6,8],"byte":[11,17],"case":[1,3,8,14,15,16],"class":[0,1,8,11,13,16,17],"default":[1,4,8,14,15],"do":[1,8,11,12,14,15,16],"final":[1,3,8,13,16],"float":[1,3,8,9],"function":[1,8,9,13,17],"garc\u00eda":5,"import":[1,5,12,13,14,15,16,17],"int":[1,3,4,6,8,9],"n\u00ba":[11,17],"new":[1,4,8,9,11,12,13,14,16,17],"p\u00e9rez":5,"return":[1,3,4,6,8,9,11,12,13,14,16,17],"super":[1,16,17],"true":[1,5,6,8,9,11,13,14,15,16,17],"try":[13,17],"while":[1,5,13,14,15],A:[1,5,8,9,12],And:[14,17],As:[5,8,11,12,13,14],At:[1,12],Be:[1,13],But:[13,14,16,17],By:[1,4,5,8,11,12],For:[1,8,9,12,13,14,15,16],If:[1,3,4,5,8,9,12,14,16],In:[1,3,5,8,9,11,12,13,14,15,16,17],Is:8,It:[1,3,5,8,9,11,12,13,17],Its:[1,9],Not:16,Of:[14,16],On:1,One:[1,12],That:[1,3,4,8,9,13,14,16,17],The:[1,3,4,5,8,9,10,12,13,14,17],Then:[1,8,14],There:[1,8,13,15,16],These:[1,3,5,8,12,13,15],To:[1,3,4,5,7,8,9,11,12,13,14,15,16],With:[1,5,9,12,13,16],_:[1,8,9,11,12,16,17],__call__:1,__init__:[1,16,17],_channel:8,_copi:1,_dim:1,_percentag:[1,3,8,9],_size:8,_size_0:8,_size_1:8,_size_:1,a_:9,abil:5,abl:[1,12,13,16],about:[1,9,12,13,14,15,16],abov:[1,12,16],abstractnod:[0,6,9],acc:[11,17],acceler:1,accept:13,access:[1,12,13,14],accomplish:[1,3,8,15],accord:[1,9],accordingli:8,account:[1,8,15],accross:[1,9],accumul:[3,8],accuraci:[11,17],achiev:1,act:[1,8,12,14],action:1,activ:1,actual:[1,8,12,13,14,17],ad:[1,4,8,9,13,16],adam:[11,17],adapt:9,add:[0,1,8,11,13,16,17],add_data:[1,8,15,16],add_on:[0,17],addit:[1,8,9],addition:1,address:[1,12,14],admit:[8,9],advanc:[1,5,10],advantag:[14,16],affect:[1,9],after:[1,3,8,11,17],afterward:[1,8,9],again:[8,11,14,16],against:8,aim:[3,8,11],alejandro:5,algorithm:[1,8,14,16,17],all:[1,3,4,8,9,11,12,13,14,15,16,17],allow:[1,8],almost:[1,13,14,15,17],along:[1,9],alreadi:[1,8,9,11,12,13,16],also:[1,3,5,7,8,9,11,12,13,14,15,16],alter:8,although:[1,12,13,14,15,16],alwai:[1,5,8,9,11,12,13,14,15,16],among:[3,8],amount:[9,11],an:[1,3,4,5,6,8,9,11,12,13,14,15,16],ancillari:[1,14,15],ani:[1,8,9,12,13,16],anoth:[1,8,9,12,13],anymor:1,api:2,appear:[1,9,12],append:[13,14,15,16,17],appli:[1,8,9,13],approach:5,appropi:[8,9,16,17],approxim:1,ar:[1,3,4,5,7,8,9,11,12,13,15,16,17],arang:4,arbitrari:1,architectur:5,archiveprefix:5,arg:[1,6,8],argument:[1,3,6,8,9,16],aros:1,around:11,arrai:12,arrow:9,arxiv:5,assert:[1,5,8,9,12,13,14,15],assign:8,assum:[1,4,8,9],attach:[1,9,12],attribut:[1,8,16],author:5,auto_stack:[1,9,11,14,15,16,17],auto_unbind:[1,9,11,14,16,17],automat:[1,9,12,13,14],auxiliari:9,avail:[5,11,12],avoid:[1,3,8,9,14],awar:[1,13],ax:[1,8,9,12,13],axes_nam:[1,5,9,12,13,14,15,16],axi:[0,4,9,12,16,17],axis1:1,axis2:1,axis_0:1,axis_1:1,axis_2:1,axis_n:1,b:[4,8,9,13],b_1:3,b_:9,b_m:3,backpropag:[11,17],backward:[1,5,11,13,17],base:[1,4,8,9,13],basi:[0,8],basic:[1,5,12,13],batch:[1,3,4,5,8,9,11,12,13,15,16],batch_0:[4,8],batch_1:[1,8,16],batch_m:1,batch_n:[4,8,16],batch_siz:[8,11,17],becaus:[1,8,14,17],becom:[1,5,8,9],been:[1,8,9,12,16],befor:[1,8,11,12,16,17],beforehand:12,begin:[4,11,12,17],behav:[1,14],behavior:[3,8],behaviour:[1,8,9,12,14,15],being:[1,3,4,8,9,13],belong:[1,8,11,12,13],besid:[1,8,9,12],best:5,better:12,between:[1,3,4,5,8,9,12,14,15,17],big:[8,13],bigger:8,binom:4,blank:1,bmatrix:4,bodi:12,bond:[3,8],bond_dim:[5,8,11,17],bool:[1,3,6,8],border:8,both:[1,8,9,12,13,14,15,16],bottom:[8,17],boudari:8,bound:[1,3,8,9],boundari:[3,8],bracket:1,bring:11,build:[1,5,10,13],built:[5,11,13,16],bunch:[1,13,16],c_:9,cach:[1,14],calcul:14,call:[1,8,9,11,13,16,17],callabl:9,can:[1,3,4,5,7,8,9,11,12,13,14,15,16,17],cannot:[1,8,9,13,15],canon:[8,11,17],canonic:[8,11,17],canonicalize_univoc:8,carri:[1,8,9,12],cast:1,cat:17,caus:8,cdot:[3,4,8],center:8,central:[1,12,17],certain:[1,4,5,9],chang:[1,5,8,9,12,14,16,17],change_s:1,change_typ:1,channel:8,charact:1,check:[1,8,9,11,12,13,14,15,17],check_first:9,child:1,chose:15,classif:[8,11],classifi:[11,16],clone:[5,7,8],close:8,cnn:17,cnn_snake:17,cnn_snakesb:17,co:4,code:[1,5,12,17],coincid:[8,9,12],collect:[1,9,13,14],column:8,com:[5,7],combin:[1,8,12,16,17],come:[1,9,11,12,15,16],comma:9,command:[5,7],common:[1,8,11,16],compar:14,complementari:8,complet:[1,9],compon:[0,4,5,9,11,13],compos:[11,17],comput:[1,3,4,5,7,8,9,11,13,14,16,17],concaten:4,concept:5,condit:[3,8,9],configur:5,conform:1,connect:[0,1,5,8,12,13,14,15,16],connect_stack:0,consecut:[3,8],consid:8,consist:[1,9],construct:[1,5,13,14],contain:[1,8,12,13,14,15],continu:11,contract:[0,1,5,8,10,11,12,14,15,16],contract_:[0,1,13],contract_between:[0,1,13],contract_between_:[0,1,13],contract_edg:[0,1,13],contract_edges_ip:[1,9],contrast:8,contrat:8,control:[1,12,14],conv2d:[8,17],conv_mp:8,conv_mps_lay:8,conv_pep:8,conv_tre:8,conveni:[8,15],convent:[1,9,13,16],convmp:0,convmpslay:[0,17],convolut:[8,16,17],convpep:0,convtre:0,convump:0,convumpslay:0,convupep:0,convutre:0,copi:[0,1,8],core:[8,12,17],corner:8,correct:[1,14],correctli:9,correspond:[1,3,4,8,9,12,13,14,16],costli:[1,8,14],could:[8,11,12,14,16],count:11,coupl:[1,12,15],cours:[14,16],cpu:[1,11,16,17],creat:[1,5,8,9,10,13,14,15,16],creation:[5,9],crop:[1,8],crossentropyloss:[11,17],cuda:[1,11,16,17],cum:[1,3,8,9],cum_percentag:[1,3,8,9,11,17],current:[1,8],custom:[1,5,8,10],cut:[3,11,17],cutoff:[1,3,8,9],d:[4,5],d_1:3,d_n:3,dagger:[1,9],dangl:[1,9,11,12,13],data:[1,4,5,8,9,13,15,16,17],data_0:[1,15],data_1:1,data_nod:[1,13,15,16],data_node_:13,dataload:[11,17],dataset:[8,11,17],david:[4,5],ddot:4,de:[1,8,13],decompos:[3,9],decomposit:[0,1,8,9,11,13,17],decreas:1,dedic:8,deep:[5,11],deepcopi:1,def:[1,11,16,17],defin:[1,4,5,8,12,13,15,16,17],degre:4,del:[1,9],delet:[1,9],delete_nod:1,densiti:8,depend:[1,3],deriv:8,descent:[12,13],describ:[1,8,16],design:8,desir:[4,13,14,15,16],detail:[1,5,8,13],determin:5,develop:5,devic:[1,8,11,16,17],devot:14,df:1,diagon:[1,6,9],diagram:12,dictionari:1,did:17,differ:[1,5,8,9,10,11,12,13,14,16,17],differenti:[5,10,12],dilat:8,dim:[4,11,17],dimens:[1,3,4,8,9,11,12,16],dimension:[12,17],directli:[1,5,7,9,12,13,14,15],disconnect:[0,1,8,12],discret:[0,8],distinct:1,distinguish:15,distribut:[3,6,8],div:[0,13],divers:5,divid:[1,9],divis:9,divisor:9,document:[1,9],doe:[1,8,12,14],don:9,done:[1,3,8,12],down:8,down_bord:8,download:[5,7,17],drawn:6,drop_last:[11,17],dtype:1,due:8,duplic:8,dure:[1,11,14,15],dynam:8,e:[1,4,5,8,9,15],each:[1,3,4,8,9,11,12,13,14,15,16,17],earlier:12,easi:[1,5],easier:12,easili:[1,12,13,16,17],edg:[0,5,8,11,12,13,14,15,16,17],edge1:[1,9],edge2:[1,9],edgea:1,edgeb:1,edges_dict:1,effect:[1,13],effici:[1,5,12],einstein:13,einsum:[0,13],either:[8,9],element:[1,4,5,6,8,9,12],element_s:[11,17],els:[11,16,17],emb:[4,11,16],emb_a:4,emb_b:4,embed:[0,8,11,16,17],embedd:4,embedding_matric:8,empti:[0,1,8,12,13,14],enabl:[1,5,8,12,13,14],end:[1,4,9],engin:12,enhanc:5,enough:[1,8],ensur:[1,5],entail:[1,14],entangl:8,entir:1,entropi:8,enumer:[1,8],environ:8,epoch:[1,11,16,17],epoch_num:[11,17],eprint:5,equal:[1,8,16],equilibrium:11,equival:[1,4,8,12,17],error:8,essenti:[1,5],etc:[1,9,12,16],evalu:13,even:[1,3,5,8,12,14,16],evenli:[3,8],everi:[1,9,12,14,17],exact:1,exactli:1,exampl:[1,4,8,9,11,13,15,16],exceed:8,excel:5,except:[6,8],exclud:[1,15],execut:9,exist:[1,8,9,13],exp:8,expand:[1,4,16],expect:[3,8],experi:[1,5,12,14],experiment:5,explain:[1,12,13,14],explan:[1,8,9],explicitli:[1,9,15,16],explod:[3,8],explor:[5,12],express:9,extend:1,extra:[3,8,9],extract:[3,8,12],extractor:17,ey:16,eye_tensor:16,f:[11,12,13,14,15,17],facilit:[1,5,9,12],fact:[1,15,16],factor:[3,8],fail:[8,9],fals:[1,3,6,8,9,11,13,14,16,17],familiar:[5,12],fashion:13,fashionmnist:17,faster:[1,16],fastest:5,featur:[1,4,8,9,12,13,14,15,17],feature_dim:[1,16],feature_s:8,fed:17,few:5,file:5,fill:[1,6,12,13,16],finish:[12,16],finit:8,first:[1,4,5,8,9,10,12,13,14,15,17],fix:[1,12,13],flat:8,flatten:8,flavour:1,flip:17,fn_first:9,fn_next:9,folder:[5,7],follow:[1,5,7,8,9],forget:1,form:[1,8,11,12,13,15,17],former:[1,8,9],forward:[1,8,9,16,17],four:16,frac:[1,3,4,8,9],framework:5,free:8,friendli:5,from:[1,3,5,6,7,8,9,11,12,13,15,16,17],from_sid:8,from_siz:8,front:1,full:11,fulli:5,func:[1,8],functool:17,fundament:5,further:[1,5],furthermor:[1,14,16],futur:[1,14],g:[1,8,9,15],gain:5,garc:5,gaussian:8,ge:[1,3,8,9],gener:[1,14],get:[1,11,12,16,17],get_axi:[1,12],get_axis_num:[1,12],get_edg:1,git:[5,7],github:[5,7],give:[1,12,14,15],given:[1,4,8],glimps:11,go:[1,9,11,13,14,17],goal:5,goe:8,good:[1,11],gpu:[11,16],grad:[1,5,13],gradient:[1,5,8,12,13],graph:[1,12,14,16],grasp:5,greater:[1,16],greatli:5,grid:8,grid_env:8,group:9,guid:5,ha:[1,3,8,9,11,12,14,15,16,17],had:[1,9,14],hand:[1,5,8,16],happen:[1,3,8,14,17],har:5,hat:4,have:[1,3,4,8,9,11,12,13,14,15,16,17],heavi:16,height:[8,11,16],help:[1,14],henc:[1,9,11,12,13,14,16],here:[1,8,13],hidden:[1,15],high:[1,4,6],highli:5,hint:1,hold:[13,15],hood:14,horizont:8,how:[1,5,8,9,10,11,13,15,17],howev:[1,5,8,9,12,13,14],http:[5,7],hundread:13,hybrid:[5,10],i:[1,3,5,8,9,12,13,14,15,16],i_1:6,i_n:6,ideal:5,ident:8,identif:5,idx:12,ijb:[9,13],ijk:9,im:9,imag:[8,11,16,17],image_s:[11,16,17],immers:5,implement:[1,5,11,17],improv:11,in_1:3,in_channel:[8,17],in_dim:[5,8,11],in_env:8,in_featur:8,in_n:3,in_region:8,in_which_axi:1,includ:[1,5,8,12],incorpor:5,inde:[1,8],independ:[1,9],index:[1,12,14],indic:[1,3,6,8,9,12,13,14,15],infer:[1,8,9,12,14,16],inform:[1,5,8,9,12,13,14],ingredi:[1,12],inherit:[1,8,9,15],inic:16,init:13,init_method:[1,8,11,12,13,17],initi:[0,1,8,11,12,13,16,17],inlin:8,inline_input:8,inline_mat:8,inner:1,input:[1,3,8,9,11,12,13,14,15,16,17],input_edg:[1,15,16],input_nod:16,insid:[1,5,7],insight:13,instal:[2,11,12],instanc:[1,9,14,15,16],instanti:[1,3,8,12,14,15,16],instati:12,instead:[1,8,13,14,15,16,17],integ:4,integr:5,intend:[1,8],interior:8,intermedi:[1,13,15,16],intern:[8,9],intric:5,introduc:8,introduct:5,invari:[1,8,14,15],inverse_memori:1,involv:[1,9,14],is_attached_to:1,is_avail:[11,16,17],is_batch:[1,12],is_connected_to:1,is_dangl:[1,12],is_data:[1,15],is_leaf:[1,15],is_node1:1,is_result:[1,15],is_virtu:[1,15],isinst:[1,13],isn:[11,12],isometri:8,issu:1,item:[11,17],iter:[1,8,14],its:[1,5,6,8,9,12,14,16,17],itself:[1,8,16],itselv:8,j:[1,4,5,9],jkb:[9,13],jl:9,jo:5,joserapa98:[5,7],just:[1,5,8,12,14,15,16,17],k:[5,9],keep:[1,3,8,9,14],keepdim:1,kei:[1,5,12],kept:[1,3,8,9],kernel:8,kernel_s:[8,17],kerstjen:5,keyword:[1,6,8],kib:[9,13],kind:[1,11],klm:9,know:[1,12,13,14],known:16,kwarg:[1,6,8],l2_reg:[11,17],l:[4,9],label:[8,11,15,17],lambda:[11,17],larger:1,largest:1,last:[1,8,9,16,17],later:16,latter:[1,8],layer:[1,5,8,11,16,17],ldot:6,lead:[1,9,15],leaf:[1,9,15,16],leaf_nod:[1,15],learn:[1,5,9,11,12,13,14,15,16,17],learn_rat:[11,17],learnabl:13,least:[1,8,9,11,14,17],leav:[8,14],left1:13,left2:13,left:[1,5,8,9,12,13,14,15,16,17],left_bord:8,left_down_corn:8,left_nod:8,left_up_corn:8,leg:1,len:[9,16],length:[1,8],let:[11,12,13,14,16,17],level:[1,4,12],leverag:12,lfloor:4,li:[1,5,12],librari:[5,12,17],like:[0,1,5,8,11,12,13,14,15,16,17],line:[5,8,17],linear:[5,16],link:1,list:[1,3,6,8,9,12,13,16],load:[11,16],load_state_dict:16,loader:[11,17],local:[4,8],locat:[1,8],log_norm:8,logarithm:[3,8],logit:[11,17],longer:[1,14],look:1,loop:[11,16],lose:11,loss:17,loss_fun:[11,17],lot:[1,11],low:[1,4,6,8],lower:[1,3,8,9],lr:[11,17],lvert:4,m:[1,3,5,7,9],machin:[5,11],made:[1,9],mai:[1,3,5,8],main:[1,12,13,14,16],mainli:1,maintain:1,make:[1,12,13,14,15],make_tensor:[1,8],manag:[1,16],mandatori:[1,9],mani:[1,8,11,12,14,17],manipul:12,manual:[1,8,16],manual_se:[11,17],map:4,margin:8,marginalize_output:8,master:[5,7],mat:3,mat_to_mpo:0,match:[1,8,9],matric:[8,9,13,16],matrix:[1,3,4,5,8,9,11,12,14,16],mats_env:8,max:[11,17],max_bond:8,maximum:[4,8],maxpool2d:17,mayb:9,mb:[11,17],mean:[1,6,8],megabyt:[11,17],mehtod:12,member:1,memori:[1,5,10,11,12,15,16,17],mention:[8,16],method:[1,8,9,12,13,14,15,16],middl:8,middle_sit:8,might:[1,8,14,15,16],mile:4,mimic:16,minuend:9,misc:5,miscellan:[11,17],mit:5,mnist:11,mod:4,mode:[1,8,9,11,15,16,17],model:[0,1,5,10,12,13,14,15],modif:1,modifi:[1,8,13],modul:[1,5,7,8,11,12,13,16,17],modulelist:17,monomi:4,monturiol:5,mooth:5,more:[1,3,5,8,9,12,13,14,15],most:[1,5,12,15],move:[1,8,12],move_nam:1,move_to_network:[1,9],movec:9,mp:[0,3,5,11,12,13,14,15,16,17],mpo:[0,3,16],mps_data:8,mps_layer:8,mpsdata:[0,3],mpslayer:[0,5,11,16],much:[1,14,15,16,17],mul:[0,13],multi:12,multipl:[8,12,14],multipli:[8,9],must:[1,3,9,12,16],my_model:5,my_nod:[1,14],my_paramnod:1,my_stack:1,mybatch:1,n:[1,4,5,6,8,11,17],n_:[1,4],n_batch:[3,8],n_col:8,n_epoch:16,n_featur:[5,8,11,16],n_param:[11,17],n_row:8,name:[1,5,9,11,12,13,14,16,17],name_:1,necessari:[1,8,9,11,12,16,17],need:[1,8,9,11,12,13,14,16],neighbour:[1,8,13],nelement:[11,17],net2:1,net:[1,5,9,12,13,14,15],network:[0,5,8,9,10,14,15,16],neumann:8,neural:[5,8,10,11],never:[1,9,15],nevertheless:5,new_edg:[1,9],new_edgea:1,new_edgeb:1,new_mp:16,new_nodea:[1,9],new_nodeb:[1,9],new_tensor:14,next:[1,8,9,12,13,14,15],nn:[1,5,8,11,12,13,16,17],no_grad:[11,17],node1:[1,5,9,12,13,14,15],node1_ax:[1,9],node1_list:1,node1_lists_dict:1,node2:[1,5,9,12,13,14,15],node2_ax:[1,9],node3:[13,14],node:[0,3,5,6,8,10,11,14,16,17],node_0:1,node_1:1,node_:[12,13,14,15],node_left:[1,9],node_n:1,node_ref:1,node_right:[1,9],nodea:[1,9],nodeb:[1,9],nodec:[1,9],nodes_list:9,nodes_nam:1,nodesa:9,nodesb:9,nodesc:9,non:[1,8,9,13,16],none:[1,3,5,8,9,13,16],norm:[1,3,8,9],normal:[1,3,6,8,9],notabl:5,notat:12,note:[1,5,9,12,13,16],noth:[1,12,14],now:[1,12,13,14,16,17],nu_batch:8,num:1,num_batch:[11,17],num_batch_edg:[1,15,16],num_epoch:[11,17],num_epochs_canon:11,num_test:[11,17],num_train:[11,17],number:[1,3,8,9,11,13,16,17],numel:1,numer:9,nummber:1,o:5,obc:[3,8],object:[1,12,17],obtain:[8,11],oc:8,occur:[1,8,14],off:[11,14,17],offer:5,often:[1,14],onc:[1,5,8,9,14],one:[1,3,4,8,9,11,12,13,14,15,16,17],ones:[0,1,4,8,12,13,15,17],ones_lik:[11,17],onli:[1,5,8,9,12,13,14,15,16],open:8,oper:[0,1,8,12,15,16],operand:[1,12,13],opposit:8,opt_einsum:[5,9,13],optim:[1,5,17],option:[1,3,4,5,8,9,12,16],order:[1,3,8,9,12,13,14,15,16],orient:8,origin:[1,4,8,9,11,15,17],orthogon:8,ot:8,other:[1,8,9,12,13,14,15,16,17],other_left:12,other_nod:14,otherwis:[1,8,9,17],otim:4,our:[11,12,13,14,17],out:[1,8,9,16],out_1:3,out_channel:[8,17],out_dim:[5,8,11],out_env:8,out_featur:8,out_n:3,out_nod:8,out_posit:8,out_region:8,output:[1,3,4,8,9,11,16],output_nod:16,outsid:[5,7],over:[1,9,13],overcom:1,overhead:14,overload:1,overrid:[1,8,16],override_edg:1,override_nod:1,overriden:[1,13,16],own:[1,8,9,14,15],ownership:1,p:[1,5,9,11,17],packag:[5,7],pad:[8,17],pair:[1,8,17],pairwis:8,paper:[4,5,8,17],parallel:[1,8],param:[1,9,11,17],param_nod:[1,5,6,13,15],paramedg:1,paramet:[1,3,4,6,8,9,11,13,16,17],parameter:[1,8,13],parametr:[1,8,11,13,16,17],paramnod:[0,6,8,9,14,15,16],paramnode1:13,paramnode2:13,paramnode3:13,paramnodea:1,paramnodeb:1,paramstacknod:[0,9,14,15],pareja2023tensorkrowch:5,pareja:5,parent:[1,16],parenthesi:1,part:[1,8,13,15,16],partial:[8,17],partial_dens:8,particular:[1,16],pass:[8,11,16],patch:[8,16],pattern:17,pave:11,pbc:8,pep:[0,5,16],perform:[1,5,8,9,11,12,13,14,16,17],period:8,permut:[0,1,13,14],permute_:[0,1,13],phase:9,phi:4,phys_dim:8,physic:[3,8,12],pi:4,piec:[12,16],pip:[5,7,11,12],pipelin:[5,11],pixel:[8,11,16,17],place:[1,8,9,13,14,16],plai:15,pleas:5,plu:8,plug:8,point:[1,11],poli:[0,16],pool:17,posit:[8,16],possibl:[1,8,11,12,13,14],potenti:5,power:[4,8,11],poza:5,practic:[1,5],practition:11,precis:4,pred:[11,17],predict:[11,17],present:17,previou:[1,8,13,14,15,16,17],previous:13,primari:5,principl:[1,15],print:[1,8,9,11,17],probabl:11,problem:[1,14],process:[1,5,11],product:[5,8,9,11,12,14,16],profici:5,project:8,properli:16,properti:[1,8,9,12],proport:[1,3,8,9],prove:8,provid:[1,3,5,8,9,12],prune:17,pt:16,purpos:[1,12,14,15],put:[1,8,11,17],pytest:[5,7],python:[5,7,11],pytorch:[1,5,9,11,12,13,14,16,17],q:9,qr:[0,1,3,8,13],qr_:[0,1,8],quantiti:[1,3,8,9],quantum:12,qubit:8,quit:[5,17],r:[5,9],r_1:9,r_2:9,rais:1,ram:5,rand:[0,1,4,8],randint:4,randn:[0,1,4,5,8,9,12,13,14,15,16],randn_ey:[8,11,17],random:[8,9,13],random_ey:16,rang:[1,5,8,9,11,12,13,14,15,16,17],rangl:4,rank:[1,3,4,8,9,13],rapid:5,rather:[1,8,9,12,14,15],re:[1,9,11,17],reach:11,readi:16,realli:[1,15],reattach:1,reattach_edg:1,recal:[1,16],recogn:13,recommend:[1,5,8],reconnect:[1,13],recov:[1,3,8],reduc:[1,8,9,11,17],redund:[1,9],refer:[1,2,9,12,13,14,15,16],referenc:1,regard:[1,9,13],regio:8,relat:12,relev:[1,12],reli:[5,9],relu:[5,17],remain:8,remov:[1,9],renorm:[0,1,3,8,13],renorma:[1,9],repeat:[1,9],repeatedli:14,replac:1,repositori:[5,7],repres:[8,12],reproduc:[1,14],requir:[1,8,9,15],requires_grad:1,res1:13,res2:13,rescal:8,reserv:[1,9,14],reset:[1,8,9,14,15,16],reset_tensor_address:1,reshap:[3,9,14],resiz:[11,17],respect:[1,3,8,9,13,14],rest:[1,13],restrict:[1,9,15],result2:9,result:[1,3,5,8,9,13,14,15,16],resultant_nod:[1,15],retain:1,retriev:[1,12,14,15],reus:8,rez:5,rfloor:4,rho_a:8,rid:1,right1:13,right2:13,right:[1,5,8,9,12,13,14,15,16,17],right_bord:8,right_down_corn:8,right_nod:8,right_up_corn:8,rightmost:8,ring:13,role:[1,8,15,16],row:8,rowch:5,rq:[0,1],rq_:[0,1,8],run:[5,7,8,11,17],running_acc:11,running_test_acc:[11,17],running_train_acc:[11,17],running_train_loss:[11,17],s:[1,4,5,6,8,9,11,12,13,14,15,16,17],s_1:4,s_i:[1,3,8,9],s_j:4,s_n:4,sai:[1,14],same:[1,8,9,12,13,14,17],sampler:[11,17],satisfi:[1,3,8,9],save:[1,5,10,13,16],scalar:8,scale:[3,8],scenario:5,schmidt:8,schwab:4,score:[11,17],second:[1,8,9,14],secondli:16,section:[12,13,15,17],see:[1,5,8,9,15,17],seen:15,select:[1,9,12,13,16],self:[1,8,16,17],send:[11,16,17],sens:[11,15],sento:16,separ:[8,9],sepcifi:1,sequenc:[1,3,8,9,13,16],sequenti:[5,16],serv:11,set:[1,8,9,14,15,16,17],set_data_nod:[1,8,15,16],set_param:[1,8],set_tensor:[1,8,14],set_tensor_from:[1,14,15,16],sever:[1,9],shall:[1,16],shape:[1,3,4,5,6,8,9,11,12,13,14,15,16],shape_all_nodes_layer_1:8,shape_all_nodes_layer_n:8,shape_node_1_layer_1:8,shape_node_1_layer_n:8,shape_node_i1_layer_1:8,shape_node_in_layer_n:8,share:[1,8,9,12,14,15],share_tensor:[1,8],should:[1,3,4,6,8,9,12,13,14,15,16],show:12,side:[1,8,9,17],similar:[1,8,9,12,13,15,16],simpl:[5,12,13],simpli:[1,12,14,16],simplifi:[1,5],simul:12,sin:4,sinc:[1,8,9,13,14,15,16,17],singl:[1,8,9,13,16],singular:[1,3,8,9,11,13,17],site:[8,12],sites_per_lay:8,situat:[1,9,15],size:[1,4,6,8,9,12,13,15,16,17],skip:1,skipp:14,slice:[1,9,14,15],slide:1,slower:[1,14],small:13,smaller:1,smooth:5,snake:[8,17],so:[1,8,9,14,16],some:[1,3,8,9,11,12,13,14,15,16],someth:12,sometim:1,sort:[1,8,12,14,15],sourc:[1,3,4,6,8,9],space:[1,11,16,17],special:[1,13,15],specif:[1,5,15],specifi:[1,3,8,9,12,13,15],split:[0,1,3,8,12,13],split_0:[1,9],split_1:[1,9],split_:[0,1,13],split_ip:[1,9],split_ip_0:[1,9],split_ip_1:[1,9],sqrt:4,squar:[8,9],squeez:4,stack:[0,1,4,8,11,13,14,15,16],stack_0:1,stack_1:1,stack_data:[1,9,16],stack_data_memori:[1,15],stack_data_nod:13,stack_input:16,stack_nod:[1,9,13,14,15],stack_result:[13,16],stackabl:9,stacked_einsum:[0,13],stackedg:[0,9,13],stacknod:[0,9,13,15],stai:1,stand:4,start:[1,8,12,14,16,17],state:[1,5,8,11,12,14,16],state_dict:16,staticmethod:17,std:[1,6,8,11,16,17],step:[1,5,10,17],stick:1,still:[1,8,9,13],store:[1,8,9,12,15,16],stoudenmir:4,str:[1,9],straightforward:5,strength:5,stride:[8,17],string:[1,8,9,15],structur:[1,5,8,12,16,17],sub:[0,13],subclass:[1,5,10,12,15,17],subsequ:[1,9,14],subsetrandomsampl:[11,17],substitut:1,subsystem:8,subtract:9,subtrahend:9,successor:[0,9],suffix:1,suitabl:5,sum:[1,3,8,9,11,13,17],sum_:[1,3,8,9],summat:13,support:5,sv:[1,9],svd:[0,1,3,8,11],svd_:[0,1,8,13],svdr:[0,1,8],svdr_:[0,1,8],t:[8,9,11,12,15,16],t_:[6,9],tailor:5,take:[1,8,9,11,14,15,16],taken:[1,9],task:[1,8,15],temporari:[1,15],tensor:[0,3,4,5,6,8,10,15,16],tensor_address:[1,8,14,15],tensorb:9,tensorkrowch:[1,3,4,6,7,8,9,10,13,15,16,17],tensornetwork:[1,5,9,10,13,15,17],termin:[5,7],test:[5,7,11,17],test_set:[11,17],texttt:8,th:8,than:[1,3,8,9,12,16],thank:12,thei:[1,5,7,8,9,12,13,15],them:[1,8,9,11,12,13,14,16,17],themselv:[8,9],therefor:[1,5,7,8,12,16],thi:[1,3,4,5,8,9,11,12,13,14,15,16,17],thing:[1,9,11,15,17],think:14,third:8,those:[1,14,15,16],though:[1,8,9,13,16],thought:1,three:[1,9],through:[1,5,8,9,11,16,17],thu:[1,3,5,8,9,12,16],time:[1,3,4,5,8,9,10,12,13,16],titl:5,tk:[1,4,5,8,9,11,12,13,14,15,16,17],tn:16,togeth:[1,5,7,14,15],tool:[5,12,13],top:[1,5,8,11,13,17],torch:[1,3,4,5,6,8,9,11,12,13,14,15,16,17],torchvis:[11,17],total:[1,3,8,9,11,17],total_num:[11,17],totensor:[11,17],tprod:[0,13],trace:[1,8,9,11,15,16,17],trace_sit:8,track:[1,14],train:[1,5,12,13,14,16,17],train_set:[11,17],trainabl:[1,8,12,15,16,17],transform:[4,8,11,13,17],transit:8,translation:[1,8,14,15],transpos:[11,16,17],treat:1,tree:[0,16],tri:[1,9],triangular:9,trick:[14,17],tupl:[1,6,8,9],turn:[1,8,14],tutori:[1,2,11,12,13,14,15,16,17],two:[1,8,9,12,13,14],two_0:9,type:[1,3,4,5,6,8,9,10,12,13,14],typeerror:1,u:[1,6,9],ump:0,umpo:0,umpslay:0,unbind:[0,1,13,14,16],unbind_0:[1,9],unbind_result:13,unbinded_nod:[13,14],unbound:[1,9],under:[5,14],underli:14,underscor:[1,9],understand:[5,12],undesir:[1,3,8,9,15],unfold:8,uniform:[1,6,8,14,15,16],uniform_memori:16,uniform_nod:[14,15],uninform:11,uniqu:[8,14],unit:[0,8],unitari:[8,9],univoc:8,unix:[5,7],unless:1,unset:1,unset_data_nod:[1,8],unset_tensor:1,unsqueez:17,until:[1,8,9,11],up:[1,8,9,12],up_bord:8,updat:[11,17],upep:0,upon:8,upper:[8,9],ur_1sr_2v:9,us:[1,3,4,5,8,9,11,12,13,14,15,16,17],usag:1,useless:11,user:[1,5],usual:[1,8,9,15,16],usv:9,util:[11,17],utre:0,v:[1,5,7,9],valid:1,valu:[1,3,4,8,9,11,13,16,17],valueerror:1,vanilla:[12,13,14],vanish:8,vari:8,variabl:4,variant:[13,16],variat:1,variou:[5,8],vdot:4,vec:3,vec_to_mp:0,vector:[3,4,8,11,16,17],veri:[1,11,12,14,16],verifi:[1,9],versa:8,versatil:5,version:[1,8,9,13],vertic:[1,8],via:[1,3,8,9,12,13,14,15],vice:8,view:[11,16,17],virtual:[1,9,14,15,16],virtual_nod:[1,15],virtual_result:[1,15],virtual_result_stack:[1,9,15],virtual_uniform:[1,8,15,16],virtual_uniform_mp:1,visit:1,von:8,wa:[1,8,16,17],wai:[1,8,11,12,16],want:[1,8,12,13,14,16],we:[1,11,12,13,14,15,16,17],weight_decai:[11,17],well:[1,8,9],were:[1,8,9,13,15,16],what:[1,12,14,16],when:[1,8,9,11,12,13,14,15,16],whenev:[1,9],where:[1,4,8,9,11,12,13,14,16],whether:[1,3,6,8,12,14],which:[1,4,6,8,9,11,12,13,14,15,16],whilst:8,who:5,whole:[1,8,11,13,14,15,16],whose:[1,8,9,14,15],why:14,wide:[5,16],width:[8,11,16],wise:[1,9],wish:16,without:[1,6,8,9,11],won:[8,16],word:[1,12],work:[1,5,9,13,17],workflow:[1,16],worri:[1,14],would:[1,8,9,12,14,17],wouldn:15,wow:17,wrap:[1,12],written:[5,7],x:[4,5,8,11,16,17],x_1:4,x_j:4,x_n:4,y1:17,y2:17,y3:17,y4:17,y:17,yet:[12,16],you:[5,7,11,12,13,14,15,16],your:[5,7,11,12,16],your_nod:14,yourself:5,zero:[0,1,8,11,16,17],zero_grad:[11,17],zeros_lik:14},titles:["API Reference","Components","<no title>","Decompositions","Embeddings","TensorKrowch documentation","Initializers","Installation","Models","Operations","Tutorials","First Steps with TensorKrowch","Creating a Tensor Network in TensorKrowch","Contracting and Differentiating the Tensor Network","How to save Memory and Time with TensorKrowch (ADVANCED)","The different Types of Nodes (ADVANCED)","How to subclass TensorNetwork to build Custom Models","Creating a Hybrid Neural-Tensor Network Model"],titleterms:{"1":[11,12,13,14,15,16],"2":[11,12,13,14,15,16],"3":[11,12,13,14,16],"4":11,"5":11,"6":11,"7":11,"class":9,"function":11,"import":11,The:[15,16],abstractnod:1,add:9,add_on:4,advanc:[14,15],api:0,ar:14,axi:1,basi:4,between:13,build:[12,16],choos:11,cite:5,compon:[1,12,16],connect:9,connect_stack:9,content:2,contract:[9,13],contract_:9,contract_between:9,contract_between_:9,contract_edg:9,convmp:8,convmpslay:8,convpep:8,convtre:8,convump:8,convumpslay:8,convupep:8,convutre:8,copi:6,creat:[12,17],custom:16,data:11,decomposit:3,differ:15,differenti:13,disconnect:9,discret:4,distinguish:13,div:9,document:5,download:11,edg:[1,9],einsum:9,embed:4,empti:6,everyth:16,exampl:5,faster:14,first:[11,16],how:[12,14,16],hybrid:17,hyperparamet:11,initi:6,instal:[5,7],instanti:11,introduct:[11,12,13,14,15,16],librari:11,licens:5,like:9,loss:11,manag:14,mat_to_mpo:3,matrix:13,memori:14,mode:14,model:[8,11,16,17],mp:8,mpo:8,mpsdata:8,mpslayer:8,mul:9,name:15,network:[1,11,12,13,17],neural:17,node:[1,9,12,13,15],ones:6,oper:[9,13,14],optim:11,our:16,paramnod:[1,13],paramstacknod:1,pep:8,permut:9,permute_:9,poli:4,product:13,prune:11,put:16,qr:9,qr_:9,rand:6,randn:6,refer:0,renorm:9,requir:5,reserv:15,rq:9,rq_:9,run:14,save:14,set:11,setup:[11,12],skip:14,split:9,split_:9,stack:9,stacked_einsum:9,stackedg:1,stacknod:1,start:11,state:13,step:[11,12,13,14,15,16],store:14,sub:9,subclass:16,successor:1,svd:9,svd_:9,svdr:9,svdr_:9,tensor:[1,9,11,12,13,14,17],tensorkrowch:[5,11,12,14],tensornetwork:[12,14,16],time:14,togeth:16,tprod:9,train:11,tree:8,tutori:[5,10],type:15,ump:8,umpo:8,umpslay:8,unbind:9,unit:4,upep:8,utre:8,vec_to_mp:3,zero:6}}) \ No newline at end of file diff --git a/docs/_build/html/tutorials.html b/docs/_build/html/tutorials.html index b3c82c9..764e410 100644 --- a/docs/_build/html/tutorials.html +++ b/docs/_build/html/tutorials.html @@ -6,7 +6,7 @@ - Tutorials — TensorKrowch 1.0.1 documentation + Tutorials — TensorKrowch 1.1.0 documentation diff --git a/docs/_build/html/tutorials/0_first_steps.html b/docs/_build/html/tutorials/0_first_steps.html index 6694c82..ae4a22c 100644 --- a/docs/_build/html/tutorials/0_first_steps.html +++ b/docs/_build/html/tutorials/0_first_steps.html @@ -6,7 +6,7 @@ - First Steps with TensorKrowch — TensorKrowch 1.0.1 documentation + First Steps with TensorKrowch — TensorKrowch 1.1.0 documentation diff --git a/docs/_build/html/tutorials/1_creating_tensor_network.html b/docs/_build/html/tutorials/1_creating_tensor_network.html index da5840d..84193f3 100644 --- a/docs/_build/html/tutorials/1_creating_tensor_network.html +++ b/docs/_build/html/tutorials/1_creating_tensor_network.html @@ -6,7 +6,7 @@ - Creating a Tensor Network in TensorKrowch — TensorKrowch 1.0.1 documentation + Creating a Tensor Network in TensorKrowch — TensorKrowch 1.1.0 documentation diff --git a/docs/_build/html/tutorials/2_contracting_tensor_network.html b/docs/_build/html/tutorials/2_contracting_tensor_network.html index 132674c..7cf232f 100644 --- a/docs/_build/html/tutorials/2_contracting_tensor_network.html +++ b/docs/_build/html/tutorials/2_contracting_tensor_network.html @@ -6,7 +6,7 @@ - Contracting and Differentiating the Tensor Network — TensorKrowch 1.0.1 documentation + Contracting and Differentiating the Tensor Network — TensorKrowch 1.1.0 documentation @@ -496,8 +496,8 @@

    2. Operations between Nodes
  • Tensor-like: We refer to the operations one can compute using tensors in vanilla PyTorch like permute() (and the in-place variant -permute_()), tprod() (tensor product), mul(), add() -and sub().

  • +permute_()), tprod() (tensor product), mul(), div(), +add(), sub() and renormalize().

  • Node-like: We refer to the operations one will need to contract a tensor network. These we will explain in more detail in this section.

  • diff --git a/docs/_build/html/tutorials/3_memory_management.html b/docs/_build/html/tutorials/3_memory_management.html index f771be3..d51adbe 100644 --- a/docs/_build/html/tutorials/3_memory_management.html +++ b/docs/_build/html/tutorials/3_memory_management.html @@ -6,7 +6,7 @@ - How to save Memory and Time with TensorKrowch (ADVANCED) — TensorKrowch 1.0.1 documentation + How to save Memory and Time with TensorKrowch (ADVANCED) — TensorKrowch 1.1.0 documentation diff --git a/docs/_build/html/tutorials/4_types_of_nodes.html b/docs/_build/html/tutorials/4_types_of_nodes.html index dd369ac..6c57646 100644 --- a/docs/_build/html/tutorials/4_types_of_nodes.html +++ b/docs/_build/html/tutorials/4_types_of_nodes.html @@ -6,7 +6,7 @@ - The different Types of Nodes (ADVANCED) — TensorKrowch 1.0.1 documentation + The different Types of Nodes (ADVANCED) — TensorKrowch 1.1.0 documentation diff --git a/docs/_build/html/tutorials/5_subclass_tensor_network.html b/docs/_build/html/tutorials/5_subclass_tensor_network.html index 9401ef9..821f3c0 100644 --- a/docs/_build/html/tutorials/5_subclass_tensor_network.html +++ b/docs/_build/html/tutorials/5_subclass_tensor_network.html @@ -6,7 +6,7 @@ - How to subclass TensorNetwork to build Custom Models — TensorKrowch 1.0.1 documentation + How to subclass TensorNetwork to build Custom Models — TensorKrowch 1.1.0 documentation @@ -709,11 +709,11 @@

    3. First Steps with our Custom ModelTensorKrowch already comes with a handful of widely-known models that you can use:

    There are also uniform and convolutional variants of the four models mentioned above.

    diff --git a/docs/_build/html/tutorials/6_mix_with_pytorch.html b/docs/_build/html/tutorials/6_mix_with_pytorch.html index eddb757..9e8fca8 100644 --- a/docs/_build/html/tutorials/6_mix_with_pytorch.html +++ b/docs/_build/html/tutorials/6_mix_with_pytorch.html @@ -6,7 +6,7 @@ - Creating a Hybrid Neural-Tensor Network Model — TensorKrowch 1.0.1 documentation + Creating a Hybrid Neural-Tensor Network Model — TensorKrowch 1.1.0 documentation diff --git a/docs/operations.rst b/docs/operations.rst index 6643a96..e0e530e 100644 --- a/docs/operations.rst +++ b/docs/operations.rst @@ -85,6 +85,10 @@ mul ^^^ .. autofunction:: mul +div +^^^ +.. autofunction:: div + add ^^^ .. autofunction:: add @@ -93,6 +97,10 @@ sub ^^^ .. autofunction:: sub +renormalize +^^^^^^^^^^^ +.. autofunction:: renormalize + Node-like Operations -------------------- diff --git a/docs/tutorials/2_contracting_tensor_network.rst b/docs/tutorials/2_contracting_tensor_network.rst index f42b503..4c8f02b 100644 --- a/docs/tutorials/2_contracting_tensor_network.rst +++ b/docs/tutorials/2_contracting_tensor_network.rst @@ -93,8 +93,8 @@ compute between nodes. We can distinguish between two types of operations: a) **Tensor-like**: We refer to the operations one can compute using tensors in vanilla ``PyTorch`` like :func:`permute` (and the in-place variant - :func:`permute_`), :func:`tprod` (tensor product), :func:`mul`, :func:`add` - and :func:`sub`. + :func:`permute_`), :func:`tprod` (tensor product), :func:`mul`, :func:`div`, + :func:`add`, :func:`sub` and :func:`renormalize`. b) **Node-like**: We refer to the operations one will need to contract a tensor network. These we will explain in more detail in this section. diff --git a/docs/tutorials/5_subclass_tensor_network.rst b/docs/tutorials/5_subclass_tensor_network.rst index 24b4148..71e431b 100644 --- a/docs/tutorials/5_subclass_tensor_network.rst +++ b/docs/tutorials/5_subclass_tensor_network.rst @@ -311,11 +311,11 @@ With this, you can save your model and load it later:: Of course, although you can create any tensor network you like, ``TensorKrowch`` already comes with a handful of widely-known models that you can use: -* :class:`MPS` -* :class:`MPSLayer` -* :class:`MPO` -* :class:`PEPS` -* :class:`Tree` +* :class:`~tensorkrowch.models.MPS` +* :class:`~tensorkrowch.models.MPSLayer` +* :class:`~tensorkrowch.models.MPO` +* :class:`~tensorkrowch.models.PEPS` +* :class:`~tensorkrowch.models.Tree` There are also uniform and convolutional variants of the four models mentioned above. diff --git a/tensorkrowch/__init__.py b/tensorkrowch/__init__.py index 148cf27..dc87c6b 100644 --- a/tensorkrowch/__init__.py +++ b/tensorkrowch/__init__.py @@ -6,7 +6,7 @@ """ # Version -__version__ = '1.1.0' +__version__ = '1.1.1' # Network components from tensorkrowch.components import Axis @@ -33,7 +33,8 @@ from tensorkrowch.operations import Operation from tensorkrowch.operations import get_shared_edges # Not in docs -from tensorkrowch.operations import permute, permute_, tprod, mul, add, sub +from tensorkrowch.operations import (permute, permute_, tprod, mul, div, add, + sub, renormalize) from tensorkrowch.operations import (split, split_, contract_edges, contract_between, contract_between_, stack, unbind, einsum, stacked_einsum) diff --git a/tensorkrowch/components.py b/tensorkrowch/components.py index 8c95893..ba59bb3 100644 --- a/tensorkrowch/components.py +++ b/tensorkrowch/components.py @@ -814,6 +814,11 @@ def neighbours(self, axis: Optional[Ax] = None) -> Union[Optional['AbstractNode' List['AbstractNode']]: """ Returns the neighbours of the node, the nodes to which it is connected. + + If ``self`` is a ``resultant`` node, this will return the neighbours of + the ``leaf`` nodes from which ``self`` inherits the edges. Therefore, + one cannot check if two ``resultant`` nodes are connected by looking + into their neighbours lists. To do that, use :meth:`is_connected_to`. Parameters ---------- @@ -1853,7 +1858,8 @@ def std(self, axis: Optional[Union[Ax, Sequence[Ax]]] = None) -> Tensor: def norm(self, p: Union[int, float] = 2, - axis: Optional[Union[Ax, Sequence[Ax]]] = None) -> Tensor: + axis: Optional[Union[Ax, Sequence[Ax]]] = None, + keepdim: bool = False) -> Tensor: """ Returns the norm of all elements in the node's tensor. If an ``axis`` is specified, the norm is over that axis. If ``axis`` is a sequence of axes, @@ -1870,6 +1876,9 @@ def norm(self, The order of the norm. axis : int, str, Axis or list[int, str or Axis], optional Axis or sequence of axes over which to reduce. + keepdim : bool + Boolean indicating whether the output tensor have dimensions + retained or not. Returns ------- @@ -1895,7 +1904,7 @@ def norm(self, axis_num.append(self.get_axis_num(ax)) else: axis_num.append(self.get_axis_num(axis)) - return self.tensor.norm(p=p, dim=axis_num) + return self.tensor.norm(p=p, dim=axis_num, keepdim=keepdim) def numel(self) -> Tensor: """ diff --git a/tensorkrowch/decompositions/svd_decompositions.py b/tensorkrowch/decompositions/svd_decompositions.py index 5872d4f..afb31fe 100644 --- a/tensorkrowch/decompositions/svd_decompositions.py +++ b/tensorkrowch/decompositions/svd_decompositions.py @@ -14,7 +14,8 @@ def vec_to_mps(vec: torch.Tensor, n_batches: int = 0, rank: Optional[int] = None, cum_percentage: Optional[float] = None, - cutoff: Optional[float] = None) -> List[torch.Tensor]: + cutoff: Optional[float] = None, + renormalize: bool = False) -> List[torch.Tensor]: r""" Splits a vector into a sequence of MPS tensors via consecutive SVD decompositions. The resultant tensors can be used to instantiate a @@ -67,6 +68,14 @@ def vec_to_mps(vec: torch.Tensor, cum\_percentage cutoff : float, optional Quantity that lower bounds singular values in order to be kept. + renormalize : bool + Indicates whether nodes should be renormalized after SVD/QR + decompositions. If not, it may happen that the norm explodes as it + is being accumulated from all nodes. Renormalization aims to avoid + this undesired behavior by extracting the norm of each node on a + logarithmic scale after SVD/QR decompositions are computed. Finally, + the normalization factor is evenly distributed among all nodes of + the MPS. Returns ------- @@ -82,20 +91,21 @@ def vec_to_mps(vec: torch.Tensor, batches_shape = vec.shape[:n_batches] phys_dims = torch.tensor(vec.shape[n_batches:]) + log_norm = 0 prev_bond = 1 tensors = [] for i in range(len(phys_dims) - 1): - vec = vec.view(*batches_shape, - prev_bond * phys_dims[i], - phys_dims[(i + 1):].prod()) + vec = vec.reshape(*batches_shape, + prev_bond * phys_dims[i], + phys_dims[(i + 1):].prod()) u, s, vh = torch.linalg.svd(vec, full_matrices=False) lst_ranks = [] if rank is None: - rank = s.shape[-1] - lst_ranks.append(rank) + aux_rank = s.shape[-1] + lst_ranks.append(aux_rank) else: lst_ranks.append(min(max(1, int(rank)), s.shape[-1])) @@ -106,7 +116,7 @@ def vec_to_mps(vec: torch.Tensor, cp_rank = torch.lt( s_percentages, cum_percentage_tensor - ).view(-1, s.shape[-1]).all(dim=0).sum() + ).view(-1, s.shape[-1]).any(dim=0).sum() lst_ranks.append(max(1, cp_rank.item() + 1)) if cutoff is not None: @@ -114,32 +124,45 @@ def vec_to_mps(vec: torch.Tensor, co_rank = torch.ge( s, cutoff_tensor - ).view(-1, s.shape[-1]).all(dim=0).sum() + ).view(-1, s.shape[-1]).any(dim=0).sum() lst_ranks.append(max(1, co_rank.item())) # Select rank from specified restrictions - rank = min(lst_ranks) + aux_rank = min(lst_ranks) - u = u[..., :rank] + u = u[..., :aux_rank] if i > 0: - u = u.reshape(*batches_shape, prev_bond, phys_dims[i], rank) + u = u.reshape(*batches_shape, prev_bond, phys_dims[i], aux_rank) - s = s[..., :rank] - vh = vh[..., :rank, :] - vh = torch.diag_embed(s) @ vh + s = s[..., :aux_rank] + vh = vh[..., :aux_rank, :] + + if renormalize: + aux_norm = s.norm(dim=-1, keepdim=True) + if not aux_norm.isinf().any() and (aux_norm > 0).any(): + s = s / aux_norm + log_norm += aux_norm.log() tensors.append(u) - prev_bond = rank + prev_bond = aux_rank vec = torch.diag_embed(s) @ vh tensors.append(vec) + + if log_norm is not 0: + rescale = (log_norm / len(tensors)).exp() + for vec in tensors: + vec *= rescale.view(*vec.shape[:n_batches], + *([1] * len(vec.shape[n_batches:]))) + return tensors def mat_to_mpo(mat: torch.Tensor, rank: Optional[int] = None, cum_percentage: Optional[float] = None, - cutoff: Optional[float] = None) -> List[torch.Tensor]: + cutoff: Optional[float] = None, + renormalize: bool = False) -> List[torch.Tensor]: r""" Splits a matrix into a sequence of MPO tensors via consecutive SVD decompositions. The resultant tensors can be used to instantiate a @@ -179,6 +202,14 @@ def mat_to_mpo(mat: torch.Tensor, cum\_percentage cutoff : float, optional Quantity that lower bounds singular values in order to be kept. + renormalize : bool + Indicates whether nodes should be renormalized after SVD/QR + decompositions. If not, it may happen that the norm explodes as it + is being accumulated from all nodes. Renormalization aims to avoid + this undesired behavior by extracting the norm of each node on a + logarithmic scale after SVD/QR decompositions are computed. Finally, + the normalization factor is evenly distributed among all nodes of + the MPS. Returns ------- @@ -193,19 +224,20 @@ def mat_to_mpo(mat: torch.Tensor, if len(in_out_dims) == 2: return [mat] + log_norm = 0 prev_bond = 1 tensors = [] for i in range(0, len(in_out_dims) - 2, 2): - mat = mat.view(prev_bond * in_out_dims[i] * in_out_dims[i + 1], - in_out_dims[(i + 2):].prod()) + mat = mat.reshape(prev_bond * in_out_dims[i] * in_out_dims[i + 1], + in_out_dims[(i + 2):].prod()) u, s, vh = torch.linalg.svd(mat, full_matrices=False) lst_ranks = [] if rank is None: - rank = s.shape[-1] - lst_ranks.append(rank) + aux_rank = s.shape[-1] + lst_ranks.append(aux_rank) else: lst_ranks.append(min(max(1, int(rank)), s.shape[-1])) @@ -216,7 +248,7 @@ def mat_to_mpo(mat: torch.Tensor, cp_rank = torch.lt( s_percentages, cum_percentage_tensor - ).view(-1, s.shape[-1]).all(dim=0).sum() + ).view(-1, s.shape[-1]).any(dim=0).sum() lst_ranks.append(max(1, cp_rank.item() + 1)) if cutoff is not None: @@ -224,28 +256,39 @@ def mat_to_mpo(mat: torch.Tensor, co_rank = torch.ge( s, cutoff_tensor - ).view(-1, s.shape[-1]).all(dim=0).sum() + ).view(-1, s.shape[-1]).any(dim=0).sum() lst_ranks.append(max(1, co_rank.item())) # Select rank from specified restrictions - rank = min(lst_ranks) + aux_rank = min(lst_ranks) - u = u[..., :rank] + u = u[..., :aux_rank] if i == 0: - u = u.reshape(in_out_dims[i], in_out_dims[i + 1], rank) - u = u.permute(0, 2, 1) # left x input x right + u = u.reshape(in_out_dims[i], in_out_dims[i + 1], aux_rank) + u = u.permute(0, 2, 1) # input x right x output else: - u = u.reshape(prev_bond, in_out_dims[i], in_out_dims[i + 1], rank) + u = u.reshape(prev_bond, in_out_dims[i], in_out_dims[i + 1], aux_rank) u = u.permute(0, 1, 3, 2) # left x input x right x output - s = s[..., :rank] - vh = vh[..., :rank, :] - vh = torch.diag_embed(s) @ vh + s = s[..., :aux_rank] + vh = vh[..., :aux_rank, :] + + if renormalize: + aux_norm = s.norm(dim=-1) + if not aux_norm.isinf() and (aux_norm > 0): + s = s / aux_norm + log_norm += aux_norm.log() tensors.append(u) - prev_bond = rank + prev_bond = aux_rank mat = torch.diag_embed(s) @ vh - mat = mat.reshape(rank, in_out_dims[-2], in_out_dims[-1]) + mat = mat.reshape(aux_rank, in_out_dims[-2], in_out_dims[-1]) tensors.append(mat) + + if renormalize: + rescale = (log_norm / len(tensors)).exp() + for mat in tensors: + mat *= rescale + return tensors diff --git a/tensorkrowch/embeddings.py b/tensorkrowch/embeddings.py index 3c16617..63ab57c 100644 --- a/tensorkrowch/embeddings.py +++ b/tensorkrowch/embeddings.py @@ -323,6 +323,18 @@ def discretize(data: torch.Tensor, >>> emb_b = tk.embeddings.discretize(b, level=3) >>> emb_b.shape torch.Size([100, 5, 3]) + + To embed a data tensor with elements between 0 and 1 as basis vectors, one + can concatenate :func:`discretize` with :func:`basis`. + + >>> a = torch.rand(100, 10) + >>> emb_a = tk.embeddings.discretize(a, level=1, base=5) + >>> emb_a.shape + torch.Size([100, 10, 1]) + + >>> emb_a = tk.embeddings.basis(emb_a.squeeze(2).int(), dim=5) + >>> emb_a.shape + torch.Size([100, 10, 5]) """ if not isinstance(data, torch.Tensor): raise TypeError('`data` should be torch.Tensor type') @@ -412,6 +424,18 @@ def basis(data: torch.Tensor, dim: int = 2, axis: int = -1) -> torch.Tensor: >>> emb_b = tk.embeddings.basis(b, dim=10) >>> emb_b.shape torch.Size([100, 5, 10]) + + To embed a data tensor with elements between 0 and 1 as basis vectors, one + can concatenate :func:`discretize` with :func:`basis`. + + >>> a = torch.rand(100, 10) + >>> emb_a = tk.embeddings.discretize(a, level=1, base=5) + >>> emb_a.shape + torch.Size([100, 10, 1]) + + >>> emb_a = tk.embeddings.basis(emb_a.squeeze(2).int(), dim=5) + >>> emb_a.shape + torch.Size([100, 10, 5]) """ if not isinstance(data, torch.Tensor): raise TypeError('`data` should be torch.Tensor type') diff --git a/tensorkrowch/initializers.py b/tensorkrowch/initializers.py index 0092dd9..6a32f97 100644 --- a/tensorkrowch/initializers.py +++ b/tensorkrowch/initializers.py @@ -11,300 +11,197 @@ from tensorkrowch.components import TensorNetwork -def _initializer(init_method, +def _initializer(init_method: Text, shape: Shape, - axes_names: Optional[Sequence[Text]] = None, - name: Optional[Text] = None, - network: Optional[TensorNetwork] = None, param_node: bool = False, - device: Optional[torch.device] = None, - **kwargs: float) -> AbstractNode: + *args, + **kwargs) -> AbstractNode: if not param_node: - return Node(shape=shape, - axes_names=axes_names, - name=name, - network=network, - init_method=init_method, - device=device, - **kwargs) + return Node(shape=shape, init_method=init_method, *args, **kwargs) else: - return ParamNode(shape=shape, - axes_names=axes_names, - name=name, - network=network, - init_method=init_method, - device=device, - **kwargs) - + return ParamNode(shape=shape, init_method=init_method, *args, **kwargs) + def empty(shape: Shape, - axes_names: Optional[Sequence[Text]] = None, - name: Optional[Text] = None, - network: Optional[TensorNetwork] = None, param_node: bool = False, - device: Optional[torch.device] = None) -> AbstractNode: - """ - Returns :class:`Node` or :class:`ParamNode` without tensor. - - Parameters - ---------- - shape : list[int], tuple[int] or torch.Size - Node's shape, that is, the shape of its tensor. - axes_names : list[str] or tuple[str], optional - Sequence of names for each of the node's axes. Names are used to access - the edge that is attached to the node in a certain axis. Hence, they - should be all distinct. - name : str, optional - Node's name, used to access the node from de :class:`TensorNetwork` - where it belongs. It cannot contain blank spaces. - network : TensorNetwork, optional - Tensor network where the node should belong. If None, a new tensor - network, will be created to contain the node. - param_node : bool - Boolean indicating whether the node should be a :class:`ParamNode` - (``True``) or a :class:`Node` (``False``). - device : torch.device, optional - Device where to initialize the tensor. - - Returns - ------- - Node or ParamNode - """ - return _initializer(None, - shape=shape, - axes_names=axes_names, - name=name, - network=network, - param_node=param_node, - device=device) + *args, + **kwargs) -> AbstractNode: + """ + Returns :class:`Node` or :class:`ParamNode` without tensor. + + Parameters + ---------- + shape : list[int], tuple[int] or torch.Size + Node's shape, that is, the shape of its tensor. + param_node : bool + Boolean indicating whether the node should be a :class:`ParamNode` + (``True``) or a :class:`Node` (``False``). + args : + Arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + kwargs : + Keyword arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + + Returns + ------- + Node or ParamNode + """ + return _initializer(None, + shape=shape, + param_node=param_node, + *args, + **kwargs) def zeros(shape: Shape, - axes_names: Optional[Sequence[Text]] = None, - name: Optional[Text] = None, - network: Optional[TensorNetwork] = None, param_node: bool = False, - device: Optional[torch.device] = None) -> AbstractNode: - """ - Returns :class:`Node` or :class:`ParamNode` filled with zeros. - - Parameters - ---------- - shape : list[int], tuple[int] or torch.Size - Node's shape, that is, the shape of its tensor. - axes_names : list[str] or tuple[str], optional - Sequence of names for each of the node's axes. Names are used to access - the edge that is attached to the node in a certain axis. Hence, they - should be all distinct. - name : str, optional - Node's name, used to access the node from de :class:`TensorNetwork` - where it belongs. It cannot contain blank spaces. - network : TensorNetwork, optional - Tensor network where the node should belong. If None, a new tensor - network, will be created to contain the node. - param_node : bool - Boolean indicating whether the node should be a :class:`ParamNode` - (``True``) or a :class:`Node` (``False``). - device : torch.device, optional - Device where to initialize the tensor. - - Returns - ------- - Node or ParamNode - """ - return _initializer('zeros', - shape=shape, - axes_names=axes_names, - name=name, - network=network, - param_node=param_node, - device=device) + *args, + **kwargs) -> AbstractNode: + """ + Returns :class:`Node` or :class:`ParamNode` filled with zeros. + + Parameters + ---------- + shape : list[int], tuple[int] or torch.Size + Node's shape, that is, the shape of its tensor. + param_node : bool + Boolean indicating whether the node should be a :class:`ParamNode` + (``True``) or a :class:`Node` (``False``). + args : + Arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + kwargs : + Keyword arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + + Returns + ------- + Node or ParamNode + """ + return _initializer('zeros', + shape=shape, + param_node=param_node, + *args, + **kwargs) -def ones(shape: Optional[Shape] = None, - axes_names: Optional[Sequence[Text]] = None, - name: Optional[Text] = None, - network: Optional[TensorNetwork] = None, +def ones(shape: Shape, param_node: bool = False, - device: Optional[torch.device] = None) -> AbstractNode: - """ - Returns :class:`Node` or :class:`ParamNode` filled with ones. - - Parameters - ---------- - shape : list[int], tuple[int] or torch.Size - Node's shape, that is, the shape of its tensor. - axes_names : list[str] or tuple[str], optional - Sequence of names for each of the node's axes. Names are used to access - the edge that is attached to the node in a certain axis. Hence, they - should be all distinct. - name : str, optional - Node's name, used to access the node from de :class:`TensorNetwork` - where it belongs. It cannot contain blank spaces. - network : TensorNetwork, optional - Tensor network where the node should belong. If None, a new tensor - network, will be created to contain the node. - param_node : bool - Boolean indicating whether the node should be a :class:`ParamNode` - (``True``) or a :class:`Node` (``False``). - device : torch.device, optional - Device where to initialize the tensor. - - Returns - ------- - Node or ParamNode - """ - return _initializer('ones', - shape=shape, - axes_names=axes_names, - name=name, - network=network, - param_node=param_node, - device=device) + *args, + **kwargs) -> AbstractNode: + """ + Returns :class:`Node` or :class:`ParamNode` filled with ones. + + Parameters + ---------- + shape : list[int], tuple[int] or torch.Size + Node's shape, that is, the shape of its tensor. + param_node : bool + Boolean indicating whether the node should be a :class:`ParamNode` + (``True``) or a :class:`Node` (``False``). + args : + Arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + kwargs : + Keyword arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + + Returns + ------- + Node or ParamNode + """ + return _initializer('ones', + shape=shape, + param_node=param_node, + *args, + **kwargs) -def copy(shape: Optional[Shape] = None, - axes_names: Optional[Sequence[Text]] = None, - name: Optional[Text] = None, - network: Optional[TensorNetwork] = None, +def copy(shape: Shape, param_node: bool = False, - device: Optional[torch.device] = None) -> AbstractNode: - """ - Returns :class:`Node` or :class:`ParamNode` with a copy tensor, that is, - a tensor filled with zeros except in the diagonal (elements - :math:`T_{i_1 \ldots i_n}` with :math:`i_1 = \ldots = i_n`), which is - filled with ones. - - Parameters - ---------- - shape : list[int], tuple[int] or torch.Size - Node's shape, that is, the shape of its tensor. - axes_names : list[str] or tuple[str], optional - Sequence of names for each of the node's axes. Names are used to access - the edge that is attached to the node in a certain axis. Hence, they - should be all distinct. - name : str, optional - Node's name, used to access the node from de :class:`TensorNetwork` - where it belongs. It cannot contain blank spaces. - network : TensorNetwork, optional - Tensor network where the node should belong. If None, a new tensor - network, will be created to contain the node. - param_node : bool - Boolean indicating whether the node should be a :class:`ParamNode` - (``True``) or a :class:`Node` (``False``). - device : torch.device, optional - Device where to initialize the tensor. - - Returns - ------- - Node or ParamNode - """ - return _initializer('copy', - shape=shape, - axes_names=axes_names, - name=name, - network=network, - param_node=param_node, - device=device) + *args, + **kwargs) -> AbstractNode: + """ + Returns :class:`Node` or :class:`ParamNode` with a copy tensor, that is, + a tensor filled with zeros except in the diagonal (elements + :math:`T_{i_1 \ldots i_n}` with :math:`i_1 = \ldots = i_n`), which is + filled with ones. + + Parameters + ---------- + shape : list[int], tuple[int] or torch.Size + Node's shape, that is, the shape of its tensor. + param_node : bool + Boolean indicating whether the node should be a :class:`ParamNode` + (``True``) or a :class:`Node` (``False``). + args : + Arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + kwargs : + Keyword arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + + Returns + ------- + Node or ParamNode + """ + return _initializer('copy', + shape=shape, + param_node=param_node, + *args, + **kwargs) -def rand(shape: Optional[Shape] = None, - axes_names: Optional[Sequence[Text]] = None, - name: Optional[Text] = None, - network: Optional[TensorNetwork] = None, +def rand(shape: Shape, param_node: bool = False, - device: Optional[torch.device] = None, - low: float = 0., - high: float = 1., ) -> AbstractNode: - """ - Returns :class:`Node` or :class:`ParamNode` filled with elements drawn from - a uniform distribution :math:`U(low, high)`. - - Parameters - ---------- - shape : list[int], tuple[int] or torch.Size - Node's shape, that is, the shape of its tensor. - axes_names : list[str] or tuple[str], optional - Sequence of names for each of the node's axes. Names are used to access - the edge that is attached to the node in a certain axis. Hence, they - should be all distinct. - name : str, optional - Node's name, used to access the node from de :class:`TensorNetwork` - where it belongs. It cannot contain blank spaces. - network : TensorNetwork, optional - Tensor network where the node should belong. If None, a new tensor - network, will be created to contain the node. - param_node : bool - Boolean indicating whether the node should be a :class:`ParamNode` - (``True``) or a :class:`Node` (``False``). - device : torch.device, optional - Device where to initialize the tensor. - low : float - Lower limit of the uniform distribution. - high : float - Upper limit of the uniform distribution. - - Returns - ------- - Node or ParamNode - """ - return _initializer('rand', - shape=shape, - axes_names=axes_names, - name=name, - network=network, - param_node=param_node, - device=device, - low=low, - high=high) + *args, + **kwargs) -> AbstractNode: + """ + Returns :class:`Node` or :class:`ParamNode` filled with elements drawn from + a uniform distribution :math:`U(low, high)`. + + Parameters + ---------- + shape : list[int], tuple[int] or torch.Size + Node's shape, that is, the shape of its tensor. + param_node : bool + Boolean indicating whether the node should be a :class:`ParamNode` + (``True``) or a :class:`Node` (``False``). + args : + Arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + kwargs : + Keyword arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + + Returns + ------- + Node or ParamNode + """ + return _initializer('rand', + shape=shape, + param_node=param_node, + *args, + **kwargs) -def randn(shape: Optional[Shape] = None, - axes_names: Optional[Sequence[Text]] = None, - name: Optional[Text] = None, - network: Optional[TensorNetwork] = None, +def randn(shape: Shape, param_node: bool = False, - device: Optional[torch.device] = None, - mean: float = 0., - std: float = 1., ) -> AbstractNode: - """ - Returns :class:`Node` or :class:`ParamNode` filled with elements drawn from - a normal distribution :math:`N(mean, std)`. - - Parameters - ---------- - shape : list[int], tuple[int] or torch.Size - Node's shape, that is, the shape of its tensor. - axes_names : list[str] or tuple[str], optional - Sequence of names for each of the node's axes. Names are used to access - the edge that is attached to the node in a certain axis. Hence, they - should be all distinct. - name : str, optional - Node's name, used to access the node from de :class:`TensorNetwork` - where it belongs. It cannot contain blank spaces. - network : TensorNetwork, optional - Tensor network where the node should belong. If None, a new tensor - network, will be created to contain the node. - param_node : bool - Boolean indicating whether the node should be a :class:`ParamNode` - (``True``) or a :class:`Node` (``False``). - device : torch.device, optional - Device where to initialize the tensor. - mean : float - Mean of the normal distribution. - std : float - Standard deviation of the normal distribution. - - Returns - ------- - Node or ParamNode - """ - return _initializer('randn', - shape=shape, - axes_names=axes_names, - name=name, - network=network, - param_node=param_node, - device=device, - mean=mean, - std=std) + *args, + **kwargs) -> AbstractNode: + """ + Returns :class:`Node` or :class:`ParamNode` filled with elements drawn from + a normal distribution :math:`N(mean, std)`. + + Parameters + ---------- + shape : list[int], tuple[int] or torch.Size + Node's shape, that is, the shape of its tensor. + param_node : bool + Boolean indicating whether the node should be a :class:`ParamNode` + (``True``) or a :class:`Node` (``False``). + args : + Arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + kwargs : + Keyword arguments to initialize an :class:`~tensorkrowch.AbstractNode`. + + Returns + ------- + Node or ParamNode + """ + return _initializer('randn', + shape=shape, + param_node=param_node, + *args, + **kwargs) diff --git a/tensorkrowch/models/mpo.py b/tensorkrowch/models/mpo.py index 5579ec1..e175bea 100644 --- a/tensorkrowch/models/mpo.py +++ b/tensorkrowch/models/mpo.py @@ -8,6 +8,8 @@ from typing import (List, Optional, Sequence, Text, Tuple, Union) +from math import sqrt + import torch import tensorkrowch.operations as op @@ -411,10 +413,6 @@ def initialize(self, Keyword arguments for the different initialization methods. See :meth:`~tensorkrowch.AbstractNode.make_tensor`. """ - if self._boundary == 'obc': - self._left_node.set_tensor(init_method='copy', device=device) - self._right_node.set_tensor(init_method='copy', device=device) - if tensors is not None: if len(tensors) != self._n_features: raise ValueError('`tensors` should be a sequence of `n_features`' @@ -422,6 +420,10 @@ def initialize(self, if self._boundary == 'obc': tensors = tensors[:] + + if device is None: + device = tensors[0].device + if len(tensors) == 1: tensors[0] = tensors[0].reshape(1, tensors[0].shape[0], @@ -431,13 +433,13 @@ def initialize(self, else: # Left node aux_tensor = torch.zeros(*self._mats_env[0].shape, - device=tensors[0].device) + device=device) aux_tensor[0] = tensors[0] tensors[0] = aux_tensor # Right node aux_tensor = torch.zeros(*self._mats_env[-1].shape, - device=tensors[-1].device) + device=device) aux_tensor[..., 0, :] = tensors[-1] tensors[-1] = aux_tensor @@ -460,6 +462,10 @@ def initialize(self, # Right node aux_tensor[..., 0, :] = node.tensor[..., 0, :] node.tensor = aux_tensor + + if self._boundary == 'obc': + self._left_node.set_tensor(init_method='copy', device=device) + self._right_node.set_tensor(init_method='copy', device=device) def set_data_nodes(self) -> None: """ @@ -543,20 +549,23 @@ def parameterize(self, return net def _input_contraction(self, - nodes_env: List[Node], + nodes_env: List[AbstractNode], + input_nodes: List[AbstractNode], inline_input: bool = False) -> Tuple[ Optional[List[Node]], Optional[List[Node]]]: """Contracts input data nodes with MPO nodes.""" if inline_input: - mats_result = [node['input'].contract() for node in nodes_env] + mats_result = [ + in_node @ node + for node, in_node in zip(nodes_env, input_nodes) + ] return mats_result else: if nodes_env: stack = op.stack(nodes_env) - stack_data = op.stack( - [node.neighbours('input') for node in nodes_env]) + stack_data = op.stack(input_nodes) stack ^ stack_data @@ -567,15 +576,26 @@ def _input_contraction(self, return [] @staticmethod - def _inline_contraction(nodes: List[Node]) -> Node: + def _inline_contraction(mats_env: List[AbstractNode], + renormalize: bool = False) -> Node: """Contracts sequence of MPO nodes (matrices) inline.""" - result_node = nodes[0] - for node in nodes[1:]: + result_node = mats_env[0] + for node in mats_env[1:]: result_node @= node + + if renormalize: + right_axes = [] + for ax_name in result_node.axes_names: + if 'right' in ax_name: + right_axes.append(ax_name) + if right_axes: + result_node = result_node.renormalize(axis=right_axes) + return result_node def _contract_envs_inline(self, - mats_env: List[Node], + mats_env: List[AbstractNode], + renormalize: bool = False, mps: Optional[MPSData] = None) -> Node: """Contracts nodes environments inline.""" if (mps is not None) and (mps._boundary == 'obc'): @@ -585,13 +605,16 @@ def _contract_envs_inline(self, if self._boundary == 'obc': mats_env = [self._left_node] + mats_env mats_env = mats_env + [self._right_node] - return self._inline_contraction(mats_env) + return self._inline_contraction(mats_env=mats_env, + renormalize=renormalize) - def _aux_pairwise(self, nodes: List[Node]) -> Tuple[List[Node], + def _aux_pairwise(self, + mats_env: List[AbstractNode], + renormalize: bool = False) -> Tuple[List[Node], List[Node]]: """Contracts a sequence of MPO nodes (matrices) pairwise.""" - length = len(nodes) - aux_nodes = nodes + length = len(mats_env) + aux_nodes = mats_env if length > 1: half_length = length // 2 nice_length = 2 * half_length @@ -607,32 +630,48 @@ def _aux_pairwise(self, nodes: List[Node]) -> Tuple[List[Node], aux_nodes = stack1 @ stack2 aux_nodes = op.unbind(aux_nodes) + + if renormalize: + for i in range(len(aux_nodes)): + axes = [] + for ax_name in aux_nodes[i].axes_names: + if ('left' in ax_name) or ('right' in ax_name): + axes.append(ax_name) + if axes: + aux_nodes[i] = aux_nodes[i].renormalize(axis=axes) return aux_nodes, leftover - return nodes, [] + return mats_env, [] def _pairwise_contraction(self, - mats_nodes: List[Node], - mps: Optional[MPSData] = None) -> Node: + mats_env: List[Node], + mps: Optional[MPSData] = None, + renormalize: bool = False) -> Node: """Contracts nodes environments pairwise.""" - length = len(mats_nodes) - aux_nodes = mats_nodes + length = len(mats_env) + aux_nodes = mats_env if length > 1: leftovers = [] while length > 1: - aux1, aux2 = self._aux_pairwise(aux_nodes) + aux1, aux2 = self._aux_pairwise(mats_env=aux_nodes, + renormalize=renormalize) aux_nodes = aux1 leftovers = aux2 + leftovers length = len(aux1) aux_nodes = aux_nodes + leftovers - return self._pairwise_contraction(aux_nodes, mps) + return self._pairwise_contraction(mats_env=aux_nodes, + renormalize=renormalize, + mps=mps) - return self._contract_envs_inline(aux_nodes, mps) + return self._contract_envs_inline(mats_env=aux_nodes, + renormalize=renormalize, + mps=mps) def contract(self, inline_input: bool = False, inline_mats: bool = False, + renormalize: bool = False, mps: Optional[MPSData] = None) -> Node: """ Contracts the whole MPO with input data nodes. The input can be in the @@ -674,6 +713,14 @@ def contract(self, Boolean indicating whether the sequence of matrices (resultant after contracting the input ``data`` nodes) should be contracted inline or as a sequence of pairwise stacked contrations. + renormalize : bool + Indicates whether nodes should be renormalized after contraction. + If not, it may happen that the norm explodes or vanishes, as it + is being accumulated from all nodes. Renormalization aims to avoid + this undesired behavior by extracting the norm of each node on a + logarithmic scale. The renormalization only occurs when multiplying + sequences of matrices, once the `input` contractions have been + already performed, including contracting against ``MPSData``. mps : MPSData, optional MPS that is to be contracted with the MPO. New data can be put into the MPS via :meth:`MPSData.add_data`, and the MPS-MPO @@ -699,12 +746,19 @@ def contract(self, for mps_node, mpo_node in zip(mps._mats_env, self._mats_env): mps_node['feature'] ^ mpo_node['input'] - mats_env = self._input_contraction(self._mats_env, inline_input) + mats_env = self._input_contraction( + nodes_env=self._mats_env, + input_nodes=[node.neighbours('input') for node in self._mats_env], + inline_input=inline_input) if inline_mats: - result = self._contract_envs_inline(mats_env, mps) + result = self._contract_envs_inline(mats_env=mats_env, + renormalize=renormalize, + mps=mps) else: - result = self._pairwise_contraction(mats_env, mps) + result = self._pairwise_contraction(mats_env=mats_env, + renormalize=renormalize, + mps=mps) # Contract periodic edge if result.is_connected_to(result): @@ -724,6 +778,171 @@ def contract(self, result = op.permute(result, tuple(all_edges)) return result + + @torch.no_grad() + def canonicalize(self, + oc: Optional[int] = None, + mode: Text = 'svd', + rank: Optional[int] = None, + cum_percentage: Optional[float] = None, + cutoff: Optional[float] = None, + renormalize: bool = False) -> None: + r""" + Turns MPO into `canonical` form via local SVD/QR decompositions in the + same way this transformation is applied to :class:`~tensorkrowch.models.MPS`. + + To specify the new bond dimensions, the arguments ``rank``, + ``cum_percentage`` or ``cutoff`` can be specified. These will be used + equally for all SVD computations. + + If none of them are specified, the bond dimensions won't be modified + if possible. Only when the bond dimension is bigger than the physical + dimension multiplied by the other bond dimension of the node, it will + be cropped to that size. + + Parameters + ---------- + oc : int + Position of the orthogonality center. It should be between 0 and + ``n_features - 1``. + mode : {"svd", "svdr", "qr"} + Indicates which decomposition should be used to split a node after + contracting it. See more at :func:`~tensorkrowch.svd_`, + :func:`~tensorkrowch.svdr_`, :func:`~tensorkrowch.qr_`. + If mode is "qr", operation :func:`~tensorkrowch.qr_` will be + performed on nodes at the left of the output node, whilst operation + :func:`~tensorkrowch.rq_` will be used for nodes at the right. + rank : int, optional + Number of singular values to keep. + cum_percentage : float, optional + Proportion that should be satisfied between the sum of all singular + values kept and the total sum of all singular values. + + .. math:: + + \frac{\sum_{i \in \{kept\}}{s_i}}{\sum_{i \in \{all\}}{s_i}} \ge + cum\_percentage + cutoff : float, optional + Quantity that lower bounds singular values in order to be kept. + renormalize : bool + Indicates whether nodes should be renormalized after SVD/QR + decompositions. If not, it may happen that the norm explodes as it + is being accumulated from all nodes. Renormalization aims to avoid + this undesired behavior by extracting the norm of each node on a + logarithmic scale after SVD/QR decompositions are computed. Finally, + the normalization factor is evenly distributed among all nodes of + the MPO. + + Examples + -------- + >>> mpo = tk.models.MPO(n_features=4, + ... in_dim=2, + ... out_dim=2, + ... bond_dim=5) + >>> mpo.canonicalize(rank=3) + >>> mpo.bond_dim + [3, 3, 3] + """ + self.reset() + + prev_auto_stack = self._auto_stack + self.auto_stack = False + + if oc is None: + oc = self._n_features - 1 + elif (oc < 0) or (oc >= self._n_features): + raise ValueError('Orthogonality center position `oc` should be ' + 'between 0 and `n_features` - 1') + + log_norm = 0 + + nodes = self._mats_env[:] + if self._boundary == 'obc': + nodes[0].tensor[1:] = torch.zeros_like( + nodes[0].tensor[1:]) + nodes[-1].tensor[..., 1:, :] = torch.zeros_like( + nodes[-1].tensor[..., 1:, :]) + + # If mode is svd or svr and none of the args is provided, the ranks are + # kept as they were originally + keep_rank = False + if (rank is None) and (cum_percentage is None) and (cutoff is None): + keep_rank = True + + for i in range(oc): + if mode == 'svd': + result1, result2 = nodes[i]['right'].svd_( + side='right', + rank=nodes[i]['right'].size() if keep_rank else rank, + cum_percentage=cum_percentage, + cutoff=cutoff) + elif mode == 'svdr': + result1, result2 = nodes[i]['right'].svdr_( + side='right', + rank=nodes[i]['right'].size() if keep_rank else rank, + cum_percentage=cum_percentage, + cutoff=cutoff) + elif mode == 'qr': + result1, result2 = nodes[i]['right'].qr_() + else: + raise ValueError('`mode` can only be "svd", "svdr" or "qr"') + + if renormalize: + aux_norm = result2.norm() / sqrt(result2.shape[0]) + if not aux_norm.isinf() and (aux_norm > 0): + result2.tensor = result2.tensor / aux_norm + log_norm += aux_norm.log() + + result1 = result1.parameterize() + nodes[i] = result1 + nodes[i + 1] = result2 + + for i in range(len(nodes) - 1, oc, -1): + if mode == 'svd': + result1, result2 = nodes[i]['left'].svd_( + side='left', + rank=nodes[i]['left'].size() if keep_rank else rank, + cum_percentage=cum_percentage, + cutoff=cutoff) + elif mode == 'svdr': + result1, result2 = nodes[i]['left'].svdr_( + side='left', + rank=nodes[i]['left'].size() if keep_rank else rank, + cum_percentage=cum_percentage, + cutoff=cutoff) + elif mode == 'qr': + result1, result2 = nodes[i]['left'].rq_() + else: + raise ValueError('`mode` can only be "svd", "svdr" or "qr"') + + if renormalize: + aux_norm = result1.norm() / sqrt(result1.shape[0]) + if not aux_norm.isinf() and (aux_norm > 0): + result1.tensor = result1.tensor / aux_norm + log_norm += aux_norm.log() + + result2 = result2.parameterize() + nodes[i] = result2 + nodes[i - 1] = result1 + + nodes[oc] = nodes[oc].parameterize() + + # Rescale + if log_norm != 0: + rescale = (log_norm / len(nodes)).exp() + + if renormalize and (log_norm != 0): + for node in nodes: + node.tensor = node.tensor * rescale + + # Update variables + if self._boundary == 'obc': + self._bond_dim = [node['right'].size() for node in nodes[:-1]] + else: + self._bond_dim = [node['right'].size() for node in nodes] + self._mats_env = nodes + + self.auto_stack = prev_auto_stack class UMPO(MPO): # MARK: UMPO @@ -885,8 +1104,6 @@ def initialize(self, Keyword arguments for the different initialization methods. See :meth:`~tensorkrowch.AbstractNode.make_tensor`. """ - node = self.uniform_memory - if tensors is not None: self.uniform_memory.tensor = tensors[0] @@ -967,3 +1184,14 @@ def parameterize(self, node.set_tensor_from(net.uniform_memory) return net + + def canonicalize(self, + oc: Optional[int] = None, + mode: Text = 'svd', + rank: Optional[int] = None, + cum_percentage: Optional[float] = None, + cutoff: Optional[float] = None, + renormalize: bool = False) -> None: + """:meta private:""" + raise NotImplementedError( + '`canonicalize` not implemented for UMPO') diff --git a/tensorkrowch/models/mps.py b/tensorkrowch/models/mps.py index 17a575b..d2e6a20 100644 --- a/tensorkrowch/models/mps.py +++ b/tensorkrowch/models/mps.py @@ -537,9 +537,9 @@ def _make_nodes(self) -> None: name='left_node', network=self) self._right_node = ParamNode(shape=(aux_bond_dim[-1],), - axes_names=('left',), - name='right_node', - network=self) + axes_names=('left',), + name='right_node', + network=self) aux_bond_dim = aux_bond_dim + [aux_bond_dim[-1]] + [aux_bond_dim[0]] @@ -566,35 +566,88 @@ def _make_nodes(self) -> None: if i == self._n_features - 1: self._mats_env[-1]['right'] ^ self._right_node['left'] - def _make_unitaries(self, device: Optional[torch.device] = None) -> List[torch.Tensor]: + def _make_canonical(self, device: Optional[torch.device] = None) -> List[torch.Tensor]: """ Creates random unitaries to initialize the MPS in canonical form with - orthogonality center at the rightmost node.""" + orthogonality center at the rightmost node. Unitaries in nodes are + scaled so that the total norm squared of the initial MPS is the product + of all the physical dimensions. + """ tensors = [] for i, node in enumerate(self._mats_env): if self._boundary == 'obc': if i == 0: node_shape = node.shape[1:] aux_shape = node_shape + phys_dim = node_shape[0] elif i == (self._n_features - 1): node_shape = node.shape[:2] aux_shape = node_shape + phys_dim = node_shape[1] else: node_shape = node.shape aux_shape = (node.shape[:2].numel(), node.shape[2]) + phys_dim = node_shape[1] else: node_shape = node.shape aux_shape = (node.shape[:2].numel(), node.shape[2]) + phys_dim = node_shape[1] size = max(aux_shape[0], aux_shape[1]) tensor = random_unitary(size, device=device) tensor = tensor[:min(aux_shape[0], size), :min(aux_shape[1], size)] tensor = tensor.reshape(*node_shape) + if i == (self._n_features - 1): + if self._boundary == 'obc': + tensor = tensor.t() / tensor.norm() * sqrt(phys_dim) + else: + tensor = tensor / tensor.norm() * sqrt(phys_dim) + else: + tensor = tensor * sqrt(phys_dim) + tensors.append(tensor) - - if self._boundary == 'obc': - tensors[-1] = tensors[-1] / tensors[-1].norm() + return tensors + + def _make_unitaries(self, device: Optional[torch.device] = None) -> List[torch.Tensor]: + """ + Creates random unitaries to initialize the MPS nodes as stacks of + unitaries. + """ + tensors = [] + for i, node in enumerate(self._mats_env): + units = [] + size = max(node.shape[0], node.shape[2]) + if self._boundary == 'obc': + if i == 0: + size_1 = 1 + size_2 = min(node.shape[2], size) + elif i == (self._n_features - 1): + size_1 = min(node.shape[0], size) + size_2 = 1 + else: + size_1 = min(node.shape[0], size) + size_2 = min(node.shape[2], size) + else: + size_1 = min(node.shape[0], size) + size_2 = min(node.shape[2], size) + + for _ in range(node.shape[1]): + tensor = random_unitary(size, device=device) + tensor = tensor[:size_1, :size_2] + units.append(tensor) + + units = torch.stack(units, dim=1) + + if self._boundary == 'obc': + if i == 0: + tensors.append(units.squeeze(0)) + elif i == (self._n_features - 1): + tensors.append(units.squeeze(-1)) + else: + tensors.append(units) + else: + tensors.append(units) return tensors def initialize(self, @@ -617,9 +670,16 @@ def initialize(self, top of random gaussian tensors. In this case, ``std`` should be specified with a low value, e.g., ``std = 1e-9``. - * ``"unit"``: Nodes are initialized as random unitaries, so that the - MPS is in canonical form, with the orthogonality center at the - rightmost node. + * ``"unit"``: Nodes are initialized as stacks of random unitaries. This, + combined (at least) with an embedding of the inputs as elements of + the computational basis (:func:`~tensorkrowch.embeddings.discretize` + combined with :func:`~tensorkrowch.embeddings.basis`) + + * ``"canonical"```: MPS is initialized in canonical form with a squared + norm `close` to the product of all the physical dimensions (if bond + dimensions are bigger than the powers of the physical dimensions, + the norm could vary). Th orthogonality center is at the rightmost + node. Parameters ---------- @@ -629,7 +689,7 @@ def initialize(self, last ones, which can be rank-2, or rank-1 (if the first and last are the same). If ``boundary`` is ``"pbc"``, all tensors should be rank-3. - init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit"}, optional + init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit", "canonical"}, optional Initialization method. device : torch.device, optional Device where to initialize the tensors if ``init_method`` is provided. @@ -637,32 +697,34 @@ def initialize(self, Keyword arguments for the different initialization methods. See :meth:`~tensorkrowch.AbstractNode.make_tensor`. """ - if self._boundary == 'obc': - self._left_node.set_tensor(init_method='copy', device=device) - self._right_node.set_tensor(init_method='copy', device=device) - if init_method == 'unit': tensors = self._make_unitaries(device=device) + elif init_method == 'canonical': + tensors = self._make_canonical(device=device) if tensors is not None: if len(tensors) != self._n_features: - raise ValueError('`tensors` should be a sequence of `n_features`' - ' elements') + raise ValueError( + '`tensors` should be a sequence of `n_features` elements') if self._boundary == 'obc': tensors = tensors[:] + + if device is None: + device = tensors[0].device + if len(tensors) == 1: tensors[0] = tensors[0].reshape(1, -1, 1) else: # Left node aux_tensor = torch.zeros(*self._mats_env[0].shape, - device=tensors[0].device) + device=device) aux_tensor[0] = tensors[0] tensors[0] = aux_tensor # Right node aux_tensor = torch.zeros(*self._mats_env[-1].shape, - device=tensors[-1].device) + device=device) aux_tensor[..., 0] = tensors[-1] tensors[-1] = aux_tensor @@ -696,6 +758,10 @@ def initialize(self, # Right node aux_tensor[..., 0] = node.tensor[..., 0] node.tensor = aux_tensor + + if self._boundary == 'obc': + self._left_node.set_tensor(init_method='copy', device=device) + self._right_node.set_tensor(init_method='copy', device=device) def set_data_nodes(self) -> None: """ @@ -787,7 +853,7 @@ def _input_contraction(self, """Contracts input data nodes with MPS input nodes.""" if inline_input: mats_result = [ - node @ in_node + in_node @ node for node, in_node in zip(nodes_env, input_nodes) ] return mats_result @@ -807,20 +873,42 @@ def _input_contraction(self, @staticmethod def _inline_contraction(mats_env: List[AbstractNode], + renormalize: bool = False, from_left: bool = True) -> Node: """Contracts sequence of MPS nodes (matrices) inline.""" if from_left: result_node = mats_env[0] for node in mats_env[1:]: result_node @= node + + if renormalize: + right_axes = [] + for ax_name in result_node.axes_names: + if 'right' in ax_name: + right_axes.append(ax_name) + if right_axes: + result_node = result_node.renormalize(axis=right_axes) + return result_node + else: result_node = mats_env[-1] for node in mats_env[-2::-1]: result_node = node @ result_node + + if renormalize: + left_axes = [] + for ax_name in result_node.axes_names: + if 'left' in ax_name: + left_axes.append(ax_name) + if left_axes: + result_node = result_node.renormalize(axis=left_axes) + return result_node - def _contract_envs_inline(self, mats_env: List[AbstractNode]) -> Node: + def _contract_envs_inline(self, + mats_env: List[AbstractNode], + renormalize: bool = False) -> Node: """Contracts nodes environments inline.""" from_left = True if self._boundary == 'obc': @@ -829,13 +917,17 @@ def _contract_envs_inline(self, mats_env: List[AbstractNode]) -> Node: if mats_env[-1].neighbours('right') is self._right_node: mats_env = mats_env + [self._right_node] from_left = False - return self._inline_contraction(mats_env=mats_env, from_left=from_left) + return self._inline_contraction(mats_env=mats_env, + renormalize=renormalize, + from_left=from_left) - def _aux_pairwise(self, nodes: List[AbstractNode]) -> Tuple[List[Node], + def _aux_pairwise(self, + mats_env: List[AbstractNode], + renormalize: bool = False) -> Tuple[List[Node], List[Node]]: """Contracts a sequence of MPS nodes (matrices) pairwise.""" - length = len(nodes) - aux_nodes = nodes + length = len(mats_env) + aux_nodes = mats_env if length > 1: half_length = length // 2 nice_length = 2 * half_length @@ -851,30 +943,45 @@ def _aux_pairwise(self, nodes: List[AbstractNode]) -> Tuple[List[Node], aux_nodes = stack1 @ stack2 aux_nodes = op.unbind(aux_nodes) + + if renormalize: + for i in range(len(aux_nodes)): + axes = [] + for ax_name in aux_nodes[i].axes_names: + if ('left' in ax_name) or ('right' in ax_name): + axes.append(ax_name) + if axes: + aux_nodes[i] = aux_nodes[i].renormalize(axis=axes) return aux_nodes, leftover - return nodes, [] + return mats_env, [] - def _pairwise_contraction(self, mats_env: List[AbstractNode]) -> Node: + def _pairwise_contraction(self, + mats_env: List[AbstractNode], + renormalize: bool = False) -> Node: """Contracts nodes environments pairwise.""" length = len(mats_env) aux_nodes = mats_env if length > 1: leftovers = [] while length > 1: - aux1, aux2 = self._aux_pairwise(aux_nodes) + aux1, aux2 = self._aux_pairwise(mats_env=aux_nodes, + renormalize=renormalize) aux_nodes = aux1 leftovers = aux2 + leftovers length = len(aux1) aux_nodes = aux_nodes + leftovers - return self._pairwise_contraction(aux_nodes) + return self._pairwise_contraction(mats_env=aux_nodes, + renormalize=renormalize) - return self._contract_envs_inline(aux_nodes) + return self._contract_envs_inline(mats_env=aux_nodes, + renormalize=renormalize) def contract(self, inline_input: bool = False, inline_mats: bool = False, + renormalize: bool = False, marginalize_output: bool = False, embedding_matrices: Optional[ Union[torch.Tensor, @@ -936,6 +1043,15 @@ def contract(self, Boolean indicating whether the sequence of matrices (resultant after contracting the input ``data`` nodes) should be contracted inline or as a sequence of pairwise stacked contrations. + renormalize : bool + Indicates whether nodes should be renormalized after contraction. + If not, it may happen that the norm explodes or vanishes, as it + is being accumulated from all nodes. Renormalization aims to avoid + this undesired behavior by extracting the norm of each node on a + logarithmic scale. The renormalization only occurs when multiplying + sequences of matrices, once the `input` contractions have been + already performed, including contracting against embedding matrices + or MPOs when ``marginalize_output = True``. marginalize_output : bool Boolean indicating whether output nodes should be marginalized. If ``True``, after contracting all the input nodes with their @@ -1002,14 +1118,31 @@ def contract(self, input_nodes=[node.neighbours('input') for node in self.in_env], inline_input=inline_input) + # NOTE: to leave the input edges open and marginalize output + # data_nodes = [] + # for node in self.in_env: + # data_node = node.neighbours('input') + # if data_node: + # data_nodes.append(data_node) + + # if data_nodes: + # mats_in_env = self._input_contraction( + # nodes_env=self.in_env, + # input_nodes=data_nodes, + # inline_input=inline_input) + # else: + # mats_in_env = self.in_env + in_results = [] for region in in_regions: if inline_mats: result = self._contract_envs_inline( - mats_env=mats_in_env[:len(region)]) + mats_env=mats_in_env[:len(region)], + renormalize=renormalize) else: result = self._pairwise_contraction( - mats_env=mats_in_env[:len(region)]) + mats_env=mats_in_env[:len(region)], + renormalize=renormalize) mats_in_env = mats_in_env[len(region):] in_results.append(result) @@ -1043,7 +1176,8 @@ def contract(self, if not marginalize_output: # Contract all output nodes sequentially - result = self._inline_contraction(mats_env=nodes_out_env) + result = self._inline_contraction(mats_env=nodes_out_env, + renormalize=renormalize) else: # Copy output nodes sharing tensors @@ -1148,7 +1282,8 @@ def contract(self, inline_input=True) # Contract resultant matrices - result = self._inline_contraction(mats_env=mats_out_env) + result = self._inline_contraction(mats_env=mats_out_env, + renormalize=renormalize) # Contract periodic edge if result.is_connected_to(result): @@ -1189,10 +1324,12 @@ def norm(self) -> torch.Tensor: result = result.sqrt() return result - def partial_density(self, trace_sites: Sequence[int] = []) -> torch.Tensor: - """ + def partial_density(self, + trace_sites: Sequence[int] = [], + renormalize: bool = True) -> torch.Tensor: + r""" Returns de partial density matrix, tracing out the sites specified - by ``trace_sites``. + by ``trace_sites``: :math:`\rho_A`. This method internally sets ``out_features = trace_sites``, and calls the :meth:`~tensorkrowch.TensorNetwork.forward` method with @@ -1216,6 +1353,14 @@ def partial_density(self, trace_sites: Sequence[int] = []) -> torch.Tensor: nodes that should be traced to compute the density matrix. If it is empty ``[]``, the total density matrix will be returned, though this may be costly if :attr:`n_features` is big. + renormalize : bool + Indicates whether nodes should be renormalized after contraction. + If not, it may happen that the norm explodes or vanishes, as it + is being accumulated from all nodes. Renormalization aims to avoid + this undesired behavior by extracting the norm of each node on a + logarithmic scale. The renormalization only occurs when multiplying + sequences of matrices, once the `input` contractions have been + already performed. Examples -------- @@ -1267,37 +1412,48 @@ def partial_density(self, trace_sites: Sequence[int] = []) -> torch.Tensor: ] self.n_batches = len(dims) - result = self.forward(data, marginalize_output=True) + result = self.forward(data, + renormalize=renormalize, + marginalize_output=True) else: - result = self.forward(marginalize_output=True) + result = self.forward(renormalize=renormalize, + marginalize_output=True) return result @torch.no_grad() - def mi(self, - middle_site: int, - renormalize: bool = False) -> Union[float, Tuple[float]]: + def entropy(self, + middle_site: int, + renormalize: bool = False) -> Union[float, Tuple[float]]: r""" - Computes the Mutual Information between subsystems :math:`A` and - :math:`B`, :math:`\textrm{MI}(A:B)`, where :math:`A` goes from site + Computes the reduced von Neumann Entropy between subsystems :math:`A` + and :math:`B`, :math:`S(\rho_A)`, where :math:`A` goes from site 0 to ``middle_site``, and :math:`B` goes from ``middle_site + 1`` to ``n_features - 1``. - To compute the mutual information, the MPS is put into canonical form + To compute the reduced entropy, the MPS is put into canonical form with orthogonality center at ``middle_site``. Bond dimensions are not changed if possible. Only when the bond dimension is bigger than the physical dimension multiplied by the other bond dimension of the node, it will be cropped to that size. If the MPS is not normalized, it may happen that the computation of the - mutual information fails due to errors in the Singular Value + reduced entropy fails due to errors in the Singular Value Decompositions. To avoid this, it is recommended to set ``renormalize = True``. In this case, the norm of each node after the SVD is extracted in logarithmic form, and accumulated. As a result, - the function will return the tuple ``(mi, log_norm)``, which is a sort - of `scaled` mutual information. The actual mutual information could be - obtained as ``exp(log_norm) * mi``. + the function will return the tuple ``(entropy, log_norm)``, which is a + sort of `scaled` reduced entropy. This is, indeed, the reduced entropy + of a distribution, since the schmidt values are normalized to sum up + to 1. + + The actual reduced entropy, without rescaling, could be obtained as: + + .. math:: + + \exp(\texttt{log_norm})^2 \cdot S(\rho_A) - + \exp(\texttt{log_norm})^2 \cdot 2 \cdot \texttt{log_norm} Parameters ---------- @@ -1364,7 +1520,13 @@ def mi(self, result2 = result2.parameterize() nodes[i] = result2 nodes[i - 1] = result1 - + + if renormalize: + aux_norm = nodes[middle_site].norm() + if not aux_norm.isinf() and (aux_norm > 0): + nodes[middle_site].tensor = nodes[middle_site].tensor / aux_norm + log_norm += aux_norm.log() + nodes[middle_site] = nodes[middle_site].parameterize() # Compute mutual information @@ -1375,7 +1537,7 @@ def mi(self, full_matrices=False) s = s[s > 0] - mutual_info = -(s * (s.log() + log_norm)).sum() + entropy = -(s.pow(2) * s.pow(2).log()).sum() # Rescale if log_norm != 0: @@ -1395,9 +1557,9 @@ def mi(self, self.auto_stack = prev_auto_stack if renormalize: - return mutual_info, log_norm + return entropy, log_norm else: - return mutual_info + return entropy @torch.no_grad() def canonicalize(self, @@ -1885,17 +2047,39 @@ def _make_nodes(self) -> None: for node in self._mats_env: node.set_tensor_from(uniform_memory) - def _make_unitaries(self, device: Optional[torch.device] = None) -> torch.Tensor: - """Initializes MPS in canonical form.""" + def _make_canonical(self, device: Optional[torch.device] = None) -> List[torch.Tensor]: + """ + Creates random unitaries to initialize the MPS in canonical form with + orthogonality center at the rightmost node. Unitaries in nodes are + scaled so that the total norm squared of the initial MPS is the product + of all the physical dimensions. + """ node = self.uniform_memory node_shape = node.shape aux_shape = (node.shape[:2].numel(), node.shape[2]) size = max(aux_shape[0], aux_shape[1]) + phys_dim = node_shape[1] tensor = random_unitary(size, device=device) tensor = tensor[:min(aux_shape[0], size), :min(aux_shape[1], size)] tensor = tensor.reshape(*node_shape) + tensor = tensor * sqrt(phys_dim) + return tensor + + def _make_unitaries(self, device: Optional[torch.device] = None) -> List[torch.Tensor]: + """ + Creates random unitaries to initialize the MPS nodes as stacks of + unitaries. + """ + node = self.uniform_memory + node_shape = node.shape + + units = [] + for _ in range(node_shape[1]): + tensor = random_unitary(node_shape[0], device=device) + units.append(tensor) + tensor = torch.stack(units, dim=1) return tensor def initialize(self, @@ -1918,8 +2102,15 @@ def initialize(self, top of a random gaussian tensor. In this case, ``std`` should be specified with a low value, e.g., ``std = 1e-9``. - * ``"unit"``: Tensor is initialized as a random unitary, so that the - MPS is in canonical form. + * ``"unit"``: Tensor is initialized as a stack of random unitaries. This, + combined (at least) with an embedding of the inputs as elements of + the computational basis (:func:`~tensorkrowch.embeddings.discretize` + combined with :func:`~tensorkrowch.embeddings.basis`) + + * ``"canonical"```: MPS is initialized in canonical form with a squared + norm `close` to the product of all the physical dimensions (if bond + dimensions are bigger than the powers of the physical dimensions, + the norm could vary). Parameters ---------- @@ -1927,7 +2118,7 @@ def initialize(self, Sequence of a single tensor to set in each of the MPS nodes. The tensor should be rank-3, with its first and last dimensions being equal. - init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit"}, optional + init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit", "canonical"}, optional Initialization method. device : torch.device, optional Device where to initialize the tensors if ``init_method`` is provided. @@ -1939,6 +2130,8 @@ def initialize(self, if init_method == 'unit': tensors = [self._make_unitaries(device=device)] + elif init_method == 'canonical': + tensors = [self._make_canonical(device=device)] if tensors is not None: node.tensor = tensors[0] @@ -2260,13 +2453,163 @@ def out_node(self) -> ParamNode: """Returns the output node.""" return self._mats_env[self._out_position] + def _make_canonical(self, device: Optional[torch.device] = None) -> List[torch.Tensor]: + """ + Creates random unitaries to initialize the MPS in canonical form with + orthogonality center at the rightmost node. Unitaries in nodes are + scaled so that the total norm squared of the initial MPS is the product + of all the physical dimensions. + """ + # Left nodes + left_tensors = [] + for i, node in enumerate(self._mats_env[:self._out_position]): + if self._boundary == 'obc': + if i == 0: + node_shape = node.shape[1:] + aux_shape = node_shape + phys_dim = node_shape[0] + else: + node_shape = node.shape + aux_shape = (node.shape[:2].numel(), node.shape[2]) + phys_dim = node_shape[1] + else: + node_shape = node.shape + aux_shape = (node.shape[:2].numel(), node.shape[2]) + phys_dim = node_shape[1] + size = max(aux_shape[0], aux_shape[1]) + + tensor = random_unitary(size, device=device) + tensor = tensor[:min(aux_shape[0], size), :min(aux_shape[1], size)] + tensor = tensor.reshape(*node_shape) + + left_tensors.append(tensor * sqrt(phys_dim)) + + # Output node + out_tensor = torch.randn(self.out_node.shape, device=device) + phys_dim = out_tensor.shape[1] + if self._boundary == 'obc': + if self._out_position == 0: + out_tensor = out_tensor[0] + if self._out_position == (self._n_features - 1): + out_tensor = out_tensor[..., 0] + out_tensor = out_tensor / out_tensor.norm() * sqrt(phys_dim) + + # Right nodes + right_tensors = [] + for i, node in enumerate(self._mats_env[-1:self._out_position:-1]): + if self._boundary == 'obc': + if i == 0: + node_shape = node.shape[:2] + aux_shape = node_shape + phys_dim = node_shape[1] + else: + node_shape = node.shape + aux_shape = (node.shape[0], node.shape[1:].numel()) + phys_dim = node_shape[1] + else: + node_shape = node.shape + aux_shape = (node.shape[0], node.shape[1:].numel()) + phys_dim = node_shape[1] + size = max(aux_shape[0], aux_shape[1]) + + tensor = random_unitary(size, device=device) + tensor = tensor[:min(aux_shape[0], size), :min(aux_shape[1], size)] + tensor = tensor.reshape(*node_shape) + + right_tensors.append(tensor * sqrt(phys_dim)) + right_tensors.reverse() + + # All tensors + tensors = left_tensors + [out_tensor] + right_tensors + return tensors + + def _make_unitaries(self, device: Optional[torch.device] = None) -> List[torch.Tensor]: + """ + Creates random unitaries to initialize the MPS nodes as stacks of + unitaries. + """ + # Left_nodes + left_tensors = [] + for i, node in enumerate(self._mats_env[:self._out_position]): + units = [] + size = max(node.shape[0], node.shape[2]) + if self._boundary == 'obc': + if i == 0: + size_1 = 1 + size_2 = min(node.shape[2], size) + else: + size_1 = min(node.shape[0], size) + size_2 = min(node.shape[2], size) + else: + size_1 = min(node.shape[0], size) + size_2 = min(node.shape[2], size) + + for _ in range(node.shape[1]): + tensor = random_unitary(size, device=device) + tensor = tensor[:size_1, :size_2] + units.append(tensor) + + units = torch.stack(units, dim=1) + + if self._boundary == 'obc': + if i == 0: + left_tensors.append(units.squeeze(0)) + else: + left_tensors.append(units) + else: + left_tensors.append(units) + + # Output node + out_tensor = torch.randn(self.out_node.shape, device=device) + if self._boundary == 'obc': + if self._out_position == 0: + out_tensor = out_tensor[0] + if self._out_position == (self._n_features - 1): + out_tensor = out_tensor[..., 0] + + # Right nodes + right_tensors = [] + for i, node in enumerate(self._mats_env[-1:self._out_position:-1]): + units = [] + size = max(node.shape[0], node.shape[2]) + if self._boundary == 'obc': + if i == 0: + size_1 = min(node.shape[0], size) + size_2 = 1 + else: + size_1 = min(node.shape[0], size) + size_2 = min(node.shape[2], size) + else: + size_1 = min(node.shape[0], size) + size_2 = min(node.shape[2], size) + + for _ in range(node.shape[1]): + tensor = random_unitary(size, device=device).t() + tensor = tensor[:size_1, :size_2] + units.append(tensor) + + units = torch.stack(units, dim=1) + + if self._boundary == 'obc': + if i == 0: + right_tensors.append(units.squeeze(-1)) + else: + right_tensors.append(units) + else: + right_tensors.append(units) + right_tensors.reverse() + + # All tensors + tensors = left_tensors + [out_tensor] + right_tensors + return tensors + def initialize(self, tensors: Optional[Sequence[torch.Tensor]] = None, init_method: Optional[Text] = 'randn', device: Optional[torch.device] = None, **kwargs: float) -> None: """ - Initializes all the nodes of the :class:`MPSLayer`. It can be called when + Initializes all the nodes of the :class:`MPS`. It can be called when instantiating the model, or to override the existing nodes' tensors. There are different methods to initialize the nodes: @@ -2280,9 +2623,16 @@ def initialize(self, top of random gaussian tensors. In this case, ``std`` should be specified with a low value, e.g., ``std = 1e-9``. - * ``"unit"``: Nodes are initialized as random unitaries, so that the - MPS is in canonical form, with the orthogonality center at the - rightmost node. + * ``"unit"``: Nodes are initialized as stacks of random unitaries. This, + combined (at least) with an embedding of the inputs as elements of + the computational basis (:func:`~tensorkrowch.embeddings.discretize` + combined with :func:`~tensorkrowch.embeddings.basis`) + + * ``"canonical"```: MPS is initialized in canonical form with a squared + norm `close` to the product of all the physical dimensions (if bond + dimensions are bigger than the powers of the physical dimensions, + the norm could vary). Th orthogonality center is at the rightmost + node. Parameters ---------- @@ -2292,7 +2642,7 @@ def initialize(self, last ones, which can be rank-2, or rank-1 (if the first and last are the same). If ``boundary`` is ``"pbc"``, all tensors should be rank-3. - init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit"}, optional + init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit", "canonical"}, optional Initialization method. device : torch.device, optional Device where to initialize the tensors if ``init_method`` is provided. @@ -2300,12 +2650,61 @@ def initialize(self, Keyword arguments for the different initialization methods. See :meth:`~tensorkrowch.AbstractNode.make_tensor`. """ - if self._boundary == 'obc': - self._left_node.set_tensor(init_method='copy', device=device) - self._right_node.set_tensor(init_method='copy', device=device) + if init_method == 'unit': + tensors = self._make_unitaries(device=device) + elif init_method == 'canonical': + tensors = self._make_canonical(device=device) + + def initialize(self, + tensors: Optional[Sequence[torch.Tensor]] = None, + init_method: Optional[Text] = 'randn', + device: Optional[torch.device] = None, + **kwargs: float) -> None: + """ + Initializes all the nodes of the :class:`MPSLayer`. It can be called when + instantiating the model, or to override the existing nodes' tensors. + + There are different methods to initialize the nodes: + + * ``{"zeros", "ones", "copy", "rand", "randn"}``: Each node is + initialized calling :meth:`~tensorkrowch.AbstractNode.set_tensor` with + the given method, ``device`` and ``kwargs``. + + * ``"randn_eye"``: Nodes are initialized as in this + `paper `_, adding identities at the + top of random gaussian tensors. In this case, ``std`` should be + specified with a low value, e.g., ``std = 1e-9``. + + * ``"unit"``: Nodes are initialized as stacks of random unitaries. This, + combined (at least) with an embedding of the inputs as elements of + the computational basis (:func:`~tensorkrowch.embeddings.discretize` + combined with :func:`~tensorkrowch.embeddings.basis`) + * ``"canonical"```: MPS is initialized in canonical form with a squared + norm `close` to the product of all the physical dimensions (if bond + dimensions are bigger than the powers of the physical dimensions, + the norm could vary). Th orthogonality center is at the output node. + + Parameters + ---------- + tensors : list[torch.Tensor] or tuple[torch.Tensor], optional + Sequence of tensors to set in each of the MPS nodes. If ``boundary`` + is ``"obc"``, all tensors should be rank-3, except the first and + last ones, which can be rank-2, or rank-1 (if the first and last are + the same). If ``boundary`` is ``"pbc"``, all tensors should be + rank-3. + init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit", "canonical"}, optional + Initialization method. + device : torch.device, optional + Device where to initialize the tensors if ``init_method`` is provided. + kwargs : float + Keyword arguments for the different initialization methods. See + :meth:`~tensorkrowch.AbstractNode.make_tensor`. + """ if init_method == 'unit': tensors = self._make_unitaries(device=device) + elif init_method == 'canonical': + tensors = self._make_canonical(device=device) if tensors is not None: if len(tensors) != self._n_features: @@ -2314,18 +2713,22 @@ def initialize(self, if self._boundary == 'obc': tensors = tensors[:] + + if device is None: + device = tensors[0].device + if len(tensors) == 1: tensors[0] = tensors[0].reshape(1, -1, 1) else: # Left node aux_tensor = torch.zeros(*self._mats_env[0].shape, - device=tensors[0].device) + device=device) aux_tensor[0] = tensors[0] tensors[0] = aux_tensor # Right node aux_tensor = torch.zeros(*self._mats_env[-1].shape, - device=tensors[-1].device) + device=device) aux_tensor[..., 0] = tensors[-1] tensors[-1] = aux_tensor @@ -2365,6 +2768,10 @@ def initialize(self, # Right node aux_tensor[..., 0] = node.tensor[..., 0] node.tensor = aux_tensor + + if self._boundary == 'obc': + self._left_node.set_tensor(init_method='copy', device=device) + self._right_node.set_tensor(init_method='copy', device=device) def copy(self, share_tensors: bool = False) -> 'MPSLayer': """ @@ -2608,20 +3015,48 @@ def _make_nodes(self) -> None: for node in in_nodes: node.set_tensor_from(uniform_memory) + def _make_canonical(self, device: Optional[torch.device] = None) -> List[torch.Tensor]: + """ + Creates random unitaries to initialize the MPS in canonical form with + orthogonality center at the rightmost node. Unitaries in nodes are + scaled so that the total norm squared of the initial MPS is the product + of all the physical dimensions. + """ + # Uniform node + node = self.uniform_memory + node_shape = node.shape + aux_shape = (node.shape[:2].numel(), node.shape[2]) + + size = max(aux_shape[0], aux_shape[1]) + phys_dim = node_shape[1] + + uni_tensor = random_unitary(size, device=device) + uni_tensor = uni_tensor[:min(aux_shape[0], size), :min(aux_shape[1], size)] + uni_tensor = uni_tensor.reshape(*node_shape) + uni_tensor = uni_tensor * sqrt(phys_dim) + + # Output node + out_tensor = torch.randn(self.out_node.shape, device=device) + out_tensor = out_tensor / out_tensor.norm() * sqrt(out_tensor.shape[1]) + + return [uni_tensor, out_tensor] + def _make_unitaries(self, device: Optional[torch.device] = None) -> List[torch.Tensor]: - """Initializes MPS in canonical form.""" + """ + Creates random unitaries to initialize the MPS nodes as stacks of + unitaries. + """ tensors = [] for node in [self.uniform_memory, self.out_node]: node_shape = node.shape - aux_shape = (node.shape[:2].numel(), node.shape[2]) - - size = max(aux_shape[0], aux_shape[1]) - tensor = random_unitary(size, device=device) - tensor = tensor[:min(aux_shape[0], size), :min(aux_shape[1], size)] - tensor = tensor.reshape(*node_shape) + units = [] + for _ in range(node_shape[1]): + tensor = random_unitary(node_shape[0], device=device) + units.append(tensor) - tensors.append(tensor) + tensors.append(torch.stack(units, dim=1)) + return tensors def initialize(self, @@ -2643,9 +3078,16 @@ def initialize(self, `paper `_, adding identities at the top of a random gaussian tensor. In this case, ``std`` should be specified with a low value, e.g., ``std = 1e-9``. + + * ``"unit"``: Tensor is initialized as a stack of random unitaries. This, + combined (at least) with an embedding of the inputs as elements of + the computational basis (:func:`~tensorkrowch.embeddings.discretize` + combined with :func:`~tensorkrowch.embeddings.basis`) - * ``"unit"``: Tensor is initialized as a random unitary, so that the - MPS is in canonical form. + * ``"canonical"```: MPS is initialized in canonical form with a squared + norm `close` to the product of all the physical dimensions (if bond + dimensions are bigger than the powers of the physical dimensions, + the norm could vary). Parameters ---------- @@ -2654,7 +3096,7 @@ def initialize(self, that will be set in all input nodes, and the second one will be the output node's tensor. Both tensors should be rank-3, with all their first and last dimensions being equal. - init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit"}, optional + init_method : {"zeros", "ones", "copy", "rand", "randn", "randn_eye", "unit", "canonical"}, optional Initialization method. device : torch.device, optional Device where to initialize the tensors if ``init_method`` is provided. @@ -2664,6 +3106,8 @@ def initialize(self, """ if init_method == 'unit': tensors = self._make_unitaries(device=device) + elif init_method == 'canonical': + tensors = self._make_canonical(device=device) if tensors is not None: self.uniform_memory.tensor = tensors[0] @@ -2908,7 +3352,7 @@ def forward(self, image, mode='flat', *args, **kwargs): class ConvMPS(AbstractConvClass, MPS): # MARK: ConvMPS - """ + r""" Convolutional version of :class:`MPS`, where the input data is assumed to be a batch of images. @@ -3013,7 +3457,7 @@ def in_channels(self) -> int: @property def kernel_size(self) -> Tuple[int, int]: - """ + r""" Returns ``kernel_size``. Number of nodes is given by :math:`kernel\_size_0 \cdot kernel\_size_1`. """ @@ -3178,7 +3622,7 @@ def in_channels(self) -> int: @property def kernel_size(self) -> Tuple[int, int]: - """ + r""" Returns ``kernel_size``. Number of nodes is given by :math:`kernel\_size_0 \cdot kernel\_size_1`. """ @@ -3245,7 +3689,7 @@ def copy(self, share_tensors: bool = False) -> 'ConvUMPS': class ConvMPSLayer(AbstractConvClass, MPSLayer): # MARK: ConvMPSLayer - """ + r""" Convolutional version of :class:`MPSLayer`, where the input data is assumed to be a batch of images. @@ -3368,7 +3812,7 @@ def out_channels(self) -> int: @property def kernel_size(self) -> Tuple[int, int]: - """ + r""" Returns ``kernel_size``. Number of nodes is given by :math:`kernel\_size_0 \cdot kernel\_size_1 + 1`. """ @@ -3439,7 +3883,7 @@ def copy(self, share_tensors: bool = False) -> 'ConvMPSLayer': class ConvUMPSLayer(AbstractConvClass, UMPSLayer): # MARK: ConvUMPSLayer - """ + r""" Convolutional version of :class:`UMPSLayer`, where the input data is assumed to be a batch of images. @@ -3555,7 +3999,7 @@ def out_channels(self) -> int: @property def kernel_size(self) -> Tuple[int, int]: - """ + r""" Returns ``kernel_size``. Number of nodes is given by :math:`kernel\_size_0 \cdot kernel\_size_1 + 1`. """ diff --git a/tensorkrowch/models/mps_data.py b/tensorkrowch/models/mps_data.py index 0edfc9a..c15dd9d 100644 --- a/tensorkrowch/models/mps_data.py +++ b/tensorkrowch/models/mps_data.py @@ -373,7 +373,6 @@ def initialize(self, self._right_node.set_tensor(init_method='copy', device=device) if init_method is not None: - for i, node in enumerate(self._mats_env): node.set_tensor(init_method=init_method, device=device, @@ -435,28 +434,41 @@ def add_data(self, data: Sequence[torch.Tensor]) -> None: f'{node.shape[-2]} of the MPS') data = data[:] + device = data[0].device for i, node in enumerate(self._mats_env): if self._boundary == 'obc': - aux_tensor = torch.zeros(*node.shape, - device=data[i].device) + aux_tensor = torch.zeros(*node.shape, device=device) if (i == 0) and (i == (self._n_features - 1)): aux_tensor = torch.zeros(*data[i].shape[:-1], *node.shape[-3:], - device=data[i].device) + device=device) aux_tensor[..., 0, :, 0] = data[i] data[i] = aux_tensor elif i == 0: aux_tensor = torch.zeros(*data[i].shape[:-2], *node.shape[-3:-1], data[i].shape[-1], - device=data[i].device) + device=device) aux_tensor[..., 0, :, :] = data[i] data[i] = aux_tensor elif i == (self._n_features - 1): aux_tensor = torch.zeros(*data[i].shape[:-1], *node.shape[-2:], - device=data[i].device) + device=device) aux_tensor[..., 0] = data[i] data[i] = aux_tensor - node._direct_set_tensor(data[i]) \ No newline at end of file + node._direct_set_tensor(data[i]) + + # Send left and right nodes to correct device + if self._boundary == 'obc': + if self._left_node.device != device: + self._left_node.tensor = self._left_node.tensor.to(device) + if self._right_node.device != device: + self._right_node.tensor = self._right_node.tensor.to(device) + + # Update bond dim + if self._boundary == 'obc': + self._bond_dim = [node['right'].size() for node in self._mats_env[:-1]] + else: + self._bond_dim = [node['right'].size() for node in self._mats_env] diff --git a/tensorkrowch/models/peps.py b/tensorkrowch/models/peps.py index 9f491d8..9d7afb0 100644 --- a/tensorkrowch/models/peps.py +++ b/tensorkrowch/models/peps.py @@ -1276,7 +1276,7 @@ def in_channels(self) -> int: @property def kernel_size(self) -> Tuple[int, int]: - """ + r""" Returns ``kernel_size``. Number of rows and columns in the 2D grid is given by :math:`kernel\_size_0` and :math:`kernel\_size_1`, respectively. """ @@ -1454,7 +1454,7 @@ def in_channels(self) -> int: @property def kernel_size(self) -> Tuple[int, int]: - """ + r""" Returns ``kernel_size``. Number of rows and columns in the 2D grid is given by :math:`kernel\_size_0` and :math:`kernel\_size_1`, respectively. """ diff --git a/tensorkrowch/operations.py b/tensorkrowch/operations.py index d0f028d..395fca5 100644 --- a/tensorkrowch/operations.py +++ b/tensorkrowch/operations.py @@ -37,6 +37,7 @@ import types from typing import Callable +from numbers import Number from itertools import starmap import opt_einsum @@ -400,7 +401,7 @@ def _tprod_next(successor: Successor, node2: AbstractNode) -> Node: tensor1 = node1._direct_get_tensor(successor.node_ref[0], successor.index[0]) - tensor2 = node1._direct_get_tensor(successor.node_ref[1], + tensor2 = node2._direct_get_tensor(successor.node_ref[1], successor.index[1]) new_tensor = torch.outer(tensor1.flatten(), tensor2.flatten()).view(*(list(node1._shape) + @@ -488,19 +489,34 @@ def tprod(node1: AbstractNode, node2: AbstractNode) -> Node: ################################### MUL ################################## # MARK: mul def _check_first_mul(node1: AbstractNode, - node2: AbstractNode) -> Optional[Successor]: - args = (node1, node2) + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Optional[Successor]: + if isinstance(node2, AbstractNode): + args = (node1, node2) + else: + args = (node1,) successors = node1._successors.get('mul') if not successors: return None return successors.get(args) -def _mul_first(node1: AbstractNode, node2: AbstractNode) -> Node: - if node1._network != node2._network: - raise ValueError('Nodes must be in the same network') +def _mul_first(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: + is_node2 = False + if isinstance(node2, AbstractNode): + is_node2 = True + if node1._network != node2._network: + raise ValueError('Nodes must be in the same network') - new_tensor = node1.tensor * node2.tensor + if is_node2: + new_tensor = node1.tensor * node2.tensor + else: + new_tensor = node1.tensor * node2 + new_node = Node._create_resultant(axes_names=node1.axes_names, name='mul', network=node1._network, @@ -510,12 +526,21 @@ def _mul_first(node1: AbstractNode, node2: AbstractNode) -> Node: # Create successor net = node1._network - args = (node1, node2) - successor = Successor(node_ref=(node1.node_ref(), - node2.node_ref()), - index=(node1._tensor_info['index'], - node2._tensor_info['index']), - child=new_node) + + if is_node2: + args = (node1, node2) + successor = Successor(node_ref=(node1.node_ref(), + node2.node_ref()), + index=(node1._tensor_info['index'], + node2._tensor_info['index']), + child=new_node, + hints=is_node2) + else: + args = (node1,) + successor = Successor(node_ref=(node1.node_ref(),), + index=(node1._tensor_info['index'],), + child=new_node, + hints=is_node2) # Add successor to parent if 'mul' in node1._successors: @@ -529,18 +554,27 @@ def _mul_first(node1: AbstractNode, node2: AbstractNode) -> Node: # Record in inverse_memory while tracing if net._tracing: node1._record_in_inverse_memory() - node2._record_in_inverse_memory() + + if is_node2: + node2._record_in_inverse_memory() return new_node def _mul_next(successor: Successor, node1: AbstractNode, - node2: AbstractNode) -> Node: + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: + is_node2 = successor.hints tensor1 = node1._direct_get_tensor(successor.node_ref[0], successor.index[0]) - tensor2 = node1._direct_get_tensor(successor.node_ref[1], - successor.index[1]) + if is_node2: + tensor2 = node2._direct_get_tensor(successor.node_ref[1], + successor.index[1]) + else: + tensor2 = node2 + new_tensor = tensor1 * tensor2 child = successor.child child._direct_set_tensor(new_tensor) @@ -548,20 +582,32 @@ def _mul_next(successor: Successor, # Record in inverse_memory while contracting, if network is traced # (to delete memory if possible) if node1._network._traced: - node1._check_inverse_memory(successor.node_ref) - node2._check_inverse_memory(successor.node_ref) - + node1._check_inverse_memory(successor.node_ref[0]) + + if is_node2: + node2._check_inverse_memory(successor.node_ref[1]) + return child mul_op = Operation('mul', _check_first_mul, _mul_first, _mul_next) -def mul(node1: AbstractNode, node2: AbstractNode) -> Node: +def mul(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: """ Element-wise product between two nodes. It can also be performed using the operator ``*``. + It also admits to take as ``node2`` a number or tensor, that will be + multiplied by the ``node1`` tensor as ``node1.tensor * node2``. If this + is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method + of a :class:`~tensorkrowch.TensorNetwork`, this will have to be called + explicitly to contract the network, rather than relying on its internal + call via the :meth:`~tensorkrowch.TensorNetwork.forward`. + Nodes ``resultant`` from this operation are called ``"mul"``. The node that keeps information about the :class:`Successor` is ``node1``. @@ -569,8 +615,9 @@ def mul(node1: AbstractNode, node2: AbstractNode) -> Node: ---------- node1 : Node or ParamNode First node to be multiplied. Its edges will appear in the resultant node. - node2 : Node or ParamNode - Second node to be multiplied. + node2 : Node, ParamNode, torch.Tensor or number + Second node to be multiplied. It can also be a number or tensor with + appropiate shape. Returns ------- @@ -584,6 +631,13 @@ def mul(node1: AbstractNode, node2: AbstractNode) -> Node: >>> result = nodeA * nodeB >>> result.shape torch.Size([2, 3]) + + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> tensorB = torch.randn(2, 3) + >>> result = nodeA * tensorB + >>> result.shape + torch.Size([2, 3]) """ return mul_op(node1, node2) @@ -594,13 +648,21 @@ def mul(node1: AbstractNode, node2: AbstractNode) -> Node: Element-wise product between two nodes. It can also be performed using the operator ``*``. + It also admits to take as ``node2`` a number or tensor, that will be + multiplied by the ``self`` tensor as ``self.tensor * node2``. If this + is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method + of a :class:`~tensorkrowch.TensorNetwork`, this will have to be called + explicitly to contract the network, rather than relying on its internal + call via the :meth:`~tensorkrowch.TensorNetwork.forward`. + Nodes ``resultant`` from this operation are called ``"mul"``. The node that keeps information about the :class:`Successor` is ``self``. Parameters ---------- - node2 : Node or ParamNode - Second node to be multiplied. + node2 : Node, ParamNode, torch.Tensor or number + Second node to be multiplied. It can also be a number or tensor with + appropiate shape. Returns ------- @@ -614,27 +676,251 @@ def mul(node1: AbstractNode, node2: AbstractNode) -> Node: >>> result = nodeA.mul(nodeB) >>> result.shape torch.Size([2, 3]) + + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> tensorB = torch.randn(2, 3) + >>> result = nodeA.mul(tensorB) + >>> result.shape + torch.Size([2, 3]) """ AbstractNode.__mul__ = mul_node +################################### DIV ################################## +# MARK: div +def _check_first_div(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Optional[Successor]: + if isinstance(node2, AbstractNode): + args = (node1, node2) + else: + args = (node1,) + successors = node1._successors.get('div') + if not successors: + return None + return successors.get(args) + + +def _div_first(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: + is_node2 = False + if isinstance(node2, AbstractNode): + is_node2 = True + if node1._network != node2._network: + raise ValueError('Nodes must be in the same network') + + if is_node2: + new_tensor = node1.tensor / node2.tensor + else: + new_tensor = node1.tensor / node2 + + new_node = Node._create_resultant(axes_names=node1.axes_names, + name='div', + network=node1._network, + tensor=new_tensor, + edges=node1._edges, + node1_list=node1.is_node1()) + + # Create successor + net = node1._network + + if is_node2: + args = (node1, node2) + successor = Successor(node_ref=(node1.node_ref(), + node2.node_ref()), + index=(node1._tensor_info['index'], + node2._tensor_info['index']), + child=new_node, + hints=is_node2) + else: + args = (node1,) + successor = Successor(node_ref=(node1.node_ref(),), + index=(node1._tensor_info['index'],), + child=new_node, + hints=is_node2) + + # Add successor to parent + if 'div' in node1._successors: + node1._successors['div'].update({args: successor}) + else: + node1._successors['div'] = {args: successor} + + # Add operation to list of performed operations of TN + net._seq_ops.append(('div', args)) + + # Record in inverse_memory while tracing + if net._tracing: + node1._record_in_inverse_memory() + + if is_node2: + node2._record_in_inverse_memory() + + return new_node + + +def _div_next(successor: Successor, + node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: + is_node2 = successor.hints + tensor1 = node1._direct_get_tensor(successor.node_ref[0], + successor.index[0]) + if is_node2: + tensor2 = node2._direct_get_tensor(successor.node_ref[1], + successor.index[1]) + else: + tensor2 = node2 + + new_tensor = tensor1 / tensor2 + child = successor.child + child._direct_set_tensor(new_tensor) + + # Record in inverse_memory while contracting, if network is traced + # (to delete memory if possible) + if node1._network._traced: + node1._check_inverse_memory(successor.node_ref[0]) + + if is_node2: + node2._check_inverse_memory(successor.node_ref[1]) + + return child + + +div_op = Operation('div', _check_first_div, _div_first, _div_next) + + +def div(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: + """ + Element-wise division between two nodes. It can also be performed using the + operator ``/``. + + It also admits to take as ``node2`` a number or tensor, that will + divide the ``node1`` tensor as ``node1.tensor / node2``. If this + is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method + of a :class:`~tensorkrowch.TensorNetwork`, this will have to be called + explicitly to contract the network, rather than relying on its internal + call via the :meth:`~tensorkrowch.TensorNetwork.forward`. + + Nodes ``resultant`` from this operation are called ``"div"``. The node + that keeps information about the :class:`Successor` is ``node1``. + + Parameters + ---------- + node1 : Node or ParamNode + First node to be divided. Its edges will appear in the resultant node. + node2 : Node, ParamNode, torch.Tensor or number + Second node, the divisor. It can also be a number or tensor with + appropiate shape. + + Returns + ------- + Node + + Examples + -------- + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> nodeB = tk.randn((2, 3), network=net) + >>> result = nodeA / nodeB + >>> result.shape + torch.Size([2, 3]) + + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> tensorB = torch.randn(2, 3) + >>> result = nodeA / tensorB + >>> result.shape + torch.Size([2, 3]) + """ + return div_op(node1, node2) + + +div_node = copy_func(div) +div_node.__doc__ = \ + """ + Element-wise division between two nodes. It can also be performed using the + operator ``/``. + + It also admits to take as ``node2`` a number or tensor, that will + divide the ``self`` tensor as ``self.tensor / node2``. If this + is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method + of a :class:`~tensorkrowch.TensorNetwork`, this will have to be called + explicitly to contract the network, rather than relying on its internal + call via the :meth:`~tensorkrowch.TensorNetwork.forward`. + + Nodes ``resultant`` from this operation are called ``"div"``. The node + that keeps information about the :class:`Successor` is ``self``. + + Parameters + ---------- + node2 : Node, ParamNode, torch.Tensor or number + Second node, the divisor. It can also be a number or tensor with + appropiate shape. + + Returns + ------- + Node + + Examples + -------- + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> nodeB = tk.randn((2, 3), network=net) + >>> result = nodeA.div(nodeB) + >>> result.shape + torch.Size([2, 3]) + + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> tensorB = torch.randn(2, 3) + >>> result = nodeA.div(tensorB) + >>> result.shape + torch.Size([2, 3]) + """ + +AbstractNode.__truediv__ = div_node + + ################################### ADD ################################## # MARK: add def _check_first_add(node1: AbstractNode, - node2: AbstractNode) -> Optional[Successor]: - args = (node1, node2) + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Optional[Successor]: + if isinstance(node2, AbstractNode): + args = (node1, node2) + else: + args = (node1,) successors = node1._successors.get('add') if not successors: return None return successors.get(args) -def _add_first(node1: AbstractNode, node2: AbstractNode) -> Node: - if node1._network != node2._network: - raise ValueError('Nodes must be in the same network') +def _add_first(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: + is_node2 = False + if isinstance(node2, AbstractNode): + is_node2 = True + if node1._network != node2._network: + raise ValueError('Nodes must be in the same network') - new_tensor = node1.tensor + node2.tensor + if is_node2: + new_tensor = node1.tensor + node2.tensor + else: + new_tensor = node1.tensor + node2 + new_node = Node._create_resultant(axes_names=node1.axes_names, name='add', network=node1._network, @@ -644,12 +930,21 @@ def _add_first(node1: AbstractNode, node2: AbstractNode) -> Node: # Create successor net = node1._network - args = (node1, node2) - successor = Successor(node_ref=(node1.node_ref(), - node2.node_ref()), - index=(node1._tensor_info['index'], - node2._tensor_info['index']), - child=new_node) + + if is_node2: + args = (node1, node2) + successor = Successor(node_ref=(node1.node_ref(), + node2.node_ref()), + index=(node1._tensor_info['index'], + node2._tensor_info['index']), + child=new_node, + hints=is_node2) + else: + args = (node1,) + successor = Successor(node_ref=(node1.node_ref(),), + index=(node1._tensor_info['index'],), + child=new_node, + hints=is_node2) # Add successor to parent if 'add' in node1._successors: @@ -663,18 +958,27 @@ def _add_first(node1: AbstractNode, node2: AbstractNode) -> Node: # Record in inverse_memory while tracing if net._tracing: node1._record_in_inverse_memory() - node2._record_in_inverse_memory() + + if is_node2: + node2._record_in_inverse_memory() return new_node def _add_next(successor: Successor, node1: AbstractNode, - node2: AbstractNode) -> Node: + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: + is_node2 = successor.hints tensor1 = node1._direct_get_tensor(successor.node_ref[0], successor.index[0]) - tensor2 = node1._direct_get_tensor(successor.node_ref[1], - successor.index[1]) + if is_node2: + tensor2 = node2._direct_get_tensor(successor.node_ref[1], + successor.index[1]) + else: + tensor2 = node2 + new_tensor = tensor1 + tensor2 child = successor.child child._direct_set_tensor(new_tensor) @@ -682,8 +986,10 @@ def _add_next(successor: Successor, # Record in inverse_memory while contracting, if network is traced # (to delete memory if possible) if node1._network._traced: - node1._check_inverse_memory(successor.node_ref) - node2._check_inverse_memory(successor.node_ref) + node1._check_inverse_memory(successor.node_ref[0]) + + if is_node2: + node2._check_inverse_memory(successor.node_ref[1]) return child @@ -691,11 +997,21 @@ def _add_next(successor: Successor, add_op = Operation('add', _check_first_add, _add_first, _add_next) -def add(node1: AbstractNode, node2: AbstractNode) -> Node: +def add(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: """ Element-wise addition between two nodes. It can also be performed using the operator ``+``. + It also admits to take as ``node2`` a number or tensor, that will be + added to the ``node1`` tensor as ``node1.tensor + node2``. If this + is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method + of a :class:`~tensorkrowch.TensorNetwork`, this will have to be called + explicitly to contract the network, rather than relying on its internal + call via the :meth:`~tensorkrowch.TensorNetwork.forward`. + Nodes ``resultant`` from this operation are called ``"add"``. The node that keeps information about the :class:`Successor` is ``node1``. @@ -703,8 +1019,9 @@ def add(node1: AbstractNode, node2: AbstractNode) -> Node: ---------- node1 : Node or ParamNode First node to be added. Its edges will appear in the resultant node. - node2 : Node or ParamNode - Second node to be added. + node2 : Node, ParamNode, torch.Tensor or numeric + Second node to be added. It can also be a number or tensor with + appropiate shape. Returns ------- @@ -718,6 +1035,13 @@ def add(node1: AbstractNode, node2: AbstractNode) -> Node: >>> result = nodeA + nodeB >>> result.shape torch.Size([2, 3]) + + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> tensorB = torch.randn(2, 3) + >>> result = nodeA + tensorB + >>> result.shape + torch.Size([2, 3]) """ return add_op(node1, node2) @@ -728,13 +1052,21 @@ def add(node1: AbstractNode, node2: AbstractNode) -> Node: Element-wise addition between two nodes. It can also be performed using the operator ``+``. + It also admits to take as ``node2`` a number or tensor, that will be + added to the ``self`` tensor as ``self.tensor + node2``. If this + is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method + of a :class:`~tensorkrowch.TensorNetwork`, this will have to be called + explicitly to contract the network, rather than relying on its internal + call via the :meth:`~tensorkrowch.TensorNetwork.forward`. + Nodes ``resultant`` from this operation are called ``"add"``. The node that keeps information about the :class:`Successor` is ``self``. Parameters ---------- - node2 : Node or ParamNode - Second node to be multiplied. + node2 : Node, ParamNode, torch.Tensor or number + Second node to be added. It can also be a number or tensor with + appropiate shape. Returns ------- @@ -748,6 +1080,13 @@ def add(node1: AbstractNode, node2: AbstractNode) -> Node: >>> result = nodeA.add(nodeB) >>> result.shape torch.Size([2, 3]) + + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> tensorB = torch.randn(2, 3) + >>> result = nodeA.add(tensorB) + >>> result.shape + torch.Size([2, 3]) """ AbstractNode.__add__ = add_node @@ -756,19 +1095,34 @@ def add(node1: AbstractNode, node2: AbstractNode) -> Node: ################################### SUB ################################## # MARK: sub def _check_first_sub(node1: AbstractNode, - node2: AbstractNode) -> Optional[Successor]: - args = (node1, node2) + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Optional[Successor]: + if isinstance(node2, AbstractNode): + args = (node1, node2) + else: + args = (node1,) successors = node1._successors.get('sub') if not successors: return None return successors.get(args) -def _sub_first(node1: AbstractNode, node2: AbstractNode) -> Node: - if node1._network != node2._network: - raise ValueError('Nodes must be in the same network') +def _sub_first(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: + is_node2 = False + if isinstance(node2, AbstractNode): + is_node2 = True + if node1._network != node2._network: + raise ValueError('Nodes must be in the same network') - new_tensor = node1.tensor - node2.tensor + if is_node2: + new_tensor = node1.tensor - node2.tensor + else: + new_tensor = node1.tensor - node2 + new_node = Node._create_resultant(axes_names=node1.axes_names, name='sub', network=node1._network, @@ -778,12 +1132,21 @@ def _sub_first(node1: AbstractNode, node2: AbstractNode) -> Node: # Create successor net = node1._network - args = (node1, node2) - successor = Successor(node_ref=(node1.node_ref(), - node2.node_ref()), - index=(node1._tensor_info['index'], - node2._tensor_info['index']), - child=new_node) + + if is_node2: + args = (node1, node2) + successor = Successor(node_ref=(node1.node_ref(), + node2.node_ref()), + index=(node1._tensor_info['index'], + node2._tensor_info['index']), + child=new_node, + hints=is_node2) + else: + args = (node1,) + successor = Successor(node_ref=(node1.node_ref(),), + index=(node1._tensor_info['index'],), + child=new_node, + hints=is_node2) # Add successor to parent if 'sub' in node1._successors: @@ -797,18 +1160,27 @@ def _sub_first(node1: AbstractNode, node2: AbstractNode) -> Node: # Record in inverse_memory while tracing if net._tracing: node1._record_in_inverse_memory() - node2._record_in_inverse_memory() + + if is_node2: + node2._record_in_inverse_memory() return new_node def _sub_next(successor: Successor, node1: AbstractNode, - node2: AbstractNode) -> Node: + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: + is_node2 = successor.hints tensor1 = node1._direct_get_tensor(successor.node_ref[0], successor.index[0]) - tensor2 = node1._direct_get_tensor(successor.node_ref[1], - successor.index[1]) + if is_node2: + tensor2 = node2._direct_get_tensor(successor.node_ref[1], + successor.index[1]) + else: + tensor2 = node2 + new_tensor = tensor1 - tensor2 child = successor.child child._direct_set_tensor(new_tensor) @@ -816,8 +1188,10 @@ def _sub_next(successor: Successor, # Record in inverse_memory while contracting, if network is traced # (to delete memory if possible) if node1._network._traced: - node1._check_inverse_memory(successor.node_ref) - node2._check_inverse_memory(successor.node_ref) + node1._check_inverse_memory(successor.node_ref[0]) + + if is_node2: + node2._check_inverse_memory(successor.node_ref[1]) return child @@ -825,11 +1199,21 @@ def _sub_next(successor: Successor, sub_op = Operation('sub', _check_first_sub, _sub_first, _sub_next) -def sub(node1: AbstractNode, node2: AbstractNode) -> Node: +def sub(node1: AbstractNode, + node2: Union[AbstractNode, + torch.Tensor, + Number]) -> Node: """ Element-wise subtraction between two nodes. It can also be performed using the operator ``-``. + It also admits to take as ``node2`` a number or tensor, that will be + subtracted from the ``node1`` tensor as ``node1.tensor - node2``. If this + is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method + of a :class:`~tensorkrowch.TensorNetwork`, this will have to be called + explicitly to contract the network, rather than relying on its internal + call via the :meth:`~tensorkrowch.TensorNetwork.forward`. + Nodes ``resultant`` from this operation are called ``"sub"``. The node that keeps information about the :class:`Successor` is ``node1``. @@ -837,8 +1221,9 @@ def sub(node1: AbstractNode, node2: AbstractNode) -> Node: ---------- node1 : Node or ParamNode First node, minuend . Its edges will appear in the resultant node. - node2 : Node or ParamNode - Second node, subtrahend. + node2 : Node, ParamNode, torch.Tensor or number + Second node, subtrahend. It can also be a number or tensor with + appropiate shape. Returns ------- @@ -852,6 +1237,13 @@ def sub(node1: AbstractNode, node2: AbstractNode) -> Node: >>> result = nodeA - nodeB >>> result.shape torch.Size([2, 3]) + + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> tensorB = torch.randn(2, 3) + >>> result = nodeA - tensorB + >>> result.shape + torch.Size([2, 3]) """ return sub_op(node1, node2) @@ -862,13 +1254,21 @@ def sub(node1: AbstractNode, node2: AbstractNode) -> Node: Element-wise subtraction between two nodes. It can also be performed using the operator ``-``. + It also admits to take as ``node2`` a number or tensor, that will be + subtracted from the ``self`` tensor as ``self.tensor - node2``. If this + is used like this in the :meth:`~tensorkrowch.TensorNetwork.contract` method + of a :class:`~tensorkrowch.TensorNetwork`, this will have to be called + explicitly to contract the network, rather than relying on its internal + call via the :meth:`~tensorkrowch.TensorNetwork.forward`. + Nodes ``resultant`` from this operation are called ``"sub"``. The node that keeps information about the :class:`Successor` is ``self``. Parameters ---------- - node2 : Node or ParamNode - Second node, subtrahend. + node2 : Node, ParamNode, torch.Tensor or number + Second node, subtrahend. It can also be a number or tensor with + appropiate shape. Returns ------- @@ -882,11 +1282,179 @@ def sub(node1: AbstractNode, node2: AbstractNode) -> Node: >>> result = nodeA.sub(nodeB) >>> result.shape torch.Size([2, 3]) + + >>> net = tk.TensorNetwork() + >>> nodeA = tk.randn((2, 3), network=net) + >>> tensorB = torch.randn(2, 3) + >>> result = nodeA.sub(tensorB) + >>> result.shape + torch.Size([2, 3]) """ AbstractNode.__sub__ = sub_node +############################### renormalize ############################## +# MARK: renormalize +def _check_first_renormalize( + node: AbstractNode, + p: Union[int, float] = 2, + axis: Optional[Union[Ax, Sequence[Ax]]] = None) -> Optional[Successor]: + + if isinstance(axis, (tuple, list)): + axis = tuple(axis) + args = (node, p, axis) + successors = node._successors.get('renormalize') + if not successors: + return None + return successors.get(args) + + +def _renormalize_first( + node: AbstractNode, + p: Union[int, float] = 2, + axis: Optional[Union[Ax, Sequence[Ax]]] = None) -> Node: + + axis_num = [] + if axis is not None: + if isinstance(axis, (tuple, list)): + for ax in axis: + axis_num.append(node.get_axis_num(ax)) + axis = tuple(axis) + else: + axis_num.append(node.get_axis_num(axis)) + + norm = node.tensor.norm(p=p, dim=axis_num, keepdim=True) + new_tensor = node.tensor / norm + new_node = Node._create_resultant(axes_names=node.axes_names, + name='renormalize', + network=node._network, + tensor=new_tensor, + edges=node._edges, + node1_list=node.is_node1()) + + # Create successor + net = node._network + args = (node, p, axis) + successor = Successor(node_ref=node.node_ref(), + index=node._tensor_info['index'], + child=new_node, + hints=axis_num) + + # Add successor to parent + if 'renormalize' in node._successors: + node._successors['renormalize'].update({args: successor}) + else: + node._successors['renormalize'] = {args: successor} + + # Add operation to list of performed operations of TN + net._seq_ops.append(('renormalize', args)) + + # Record in inverse_memory while tracing + if net._tracing: + node._record_in_inverse_memory() + + return new_node + + +def _renormalize_next( + successor: Successor, + node: AbstractNode, + p: Union[int, float] = 2, + axis: Optional[Union[Ax, Sequence[Ax]]] = None) -> Node: + + axis_num = successor.hints + tensor = node._direct_get_tensor(successor.node_ref, + successor.index) + norm = tensor.norm(p=p, dim=axis_num, keepdim=True) + new_tensor = tensor / norm + + child = successor.child + child._direct_set_tensor(new_tensor) + + # Record in inverse_memory while contracting, if network is traced + # (to delete memory if possible) + if node._network._traced: + node._check_inverse_memory(successor.node_ref) + + return child + + +renormalize_op = Operation('renormalize', + _check_first_renormalize, + _renormalize_first, + _renormalize_next) + + +def renormalize( + node: AbstractNode, + p: Union[int, float] = 2, + axis: Optional[Union[Ax, Sequence[Ax]]] = None) -> Node: + """ + Normalizes the node with the specified norm. That is, the tensor of ``node`` + is divided by its norm. + + Different norms can be taken, specifying the argument ``p``, and accross + different dimensions, or node axes, specifying the argument ``axis``. + + See also `torch.norm() `_. + + Parameters + ---------- + node : Node or ParamNode + Node that is to be renormalized. + p : int, float + The order of the norm. + axis : int, str, Axis or list[int, str or Axis], optional + Axis or sequence of axes over which to reduce. + + Returns + ------- + Node + + Examples + -------- + >>> nodeA = tk.randn((3, 3)) + >>> renormA = tk.renormalize(nodeA) + >>> renormA.norm() + tensor(1.) + """ + return renormalize_op(node, p, axis) + + +renormalize_node = copy_func(renormalize) +renormalize_node.__doc__ = \ + """ + Normalizes the node with the specified norm. That is, the tensor of ``node`` + is divided by its norm. + + Different norms can be taken, specifying the argument ``p``, and accross + different dimensions, or node axes, specifying the argument ``axis``. + + See also `torch.norm() `_. + + Parameters + ---------- + p : int, float + The order of the norm. + axis : int, str, Axis or list[int, str or Axis], optional + Axis or sequence of axes over which to reduce. + + Returns + ------- + Node + + Examples + -------- + >>> nodeA = tk.randn((3, 3)) + >>> renormA = nodeA.renormalize() + >>> renormA.norm() + tensor(1.) + """ + +AbstractNode.renormalize = renormalize_node + + ############################################################################### # NODE-LIKE OPERATIONS # ############################################################################### @@ -1003,7 +1571,7 @@ def _split_first(node: AbstractNode, cp_rank = torch.lt( s_percentages, cum_percentage_tensor - ).view(-1, s.shape[-1]).all(dim=0).sum() + ).view(-1, s.shape[-1]).any(dim=0).sum() lst_ranks.append(max(1, cp_rank.item() + 1)) if cutoff is not None: @@ -1011,7 +1579,7 @@ def _split_first(node: AbstractNode, co_rank = torch.ge( s, cutoff_tensor - ).view(-1, s.shape[-1]).all(dim=0).sum() + ).view(-1, s.shape[-1]).any(dim=0).sum() lst_ranks.append(max(1, co_rank.item())) # Select rank from specified restrictions @@ -1202,7 +1770,7 @@ def _split_next(successor: Successor, cp_rank = torch.lt( s_percentages, cum_percentage_tensor - ).view(-1, s.shape[-1]).all(dim=0).sum() + ).view(-1, s.shape[-1]).any(dim=0).sum() lst_ranks.append(max(1, cp_rank.item() + 1)) if cutoff is not None: @@ -1210,7 +1778,7 @@ def _split_next(successor: Successor, co_rank = torch.ge( s, cutoff_tensor - ).view(-1, s.shape[-1]).all(dim=0).sum() + ).view(-1, s.shape[-1]).any(dim=0).sum() lst_ranks.append(max(1, co_rank.item())) # Select rank from specified restrictions @@ -1664,6 +2232,10 @@ def svd(edge: Edge, Contracts an edge via :func:`contract` and splits it via :func:`split` using ``mode = "svd"``. See :func:`split` for a more complete explanation. + This only works if the nodes connected through the edge are ``leaf`` nodes. + Otherwise, this will perform the contraction between the ``leaf`` nodes + that were connected through this edge. + This operation is the same as :meth:`~Edge.svd`. Parameters @@ -1787,6 +2359,10 @@ def svd(edge: Edge, Contracts an edge via :meth:`~Edge.contract` and splits it via :meth:`~AbstractNode.split` using ``mode = "svd"``. See :func:`split` for a more complete explanation. + + This only works if the nodes connected through the edge are ``leaf`` nodes. + Otherwise, this will perform the contraction between the ``leaf`` nodes + that were connected through this edge. Parameters ---------- @@ -2057,6 +2633,10 @@ def svdr(edge: Edge, Contracts an edge via :func:`contract` and splits it via :func:`split` using ``mode = "svdr"``. See :func:`split` for a more complete explanation. + This only works if the nodes connected through the edge are ``leaf`` nodes. + Otherwise, this will perform the contraction between the ``leaf`` nodes + that were connected through this edge. + This operation is the same as :meth:`~Edge.svdr`. Parameters @@ -2180,6 +2760,10 @@ def svdr(edge: Edge, Contracts an edge via :meth:`~Edge.contract` and splits it via :meth:`~AbstractNode.split` using ``mode = "svdr"``. See :func:`split` for a more complete explanation. + + This only works if the nodes connected through the edge are ``leaf`` nodes. + Otherwise, this will perform the contraction between the ``leaf`` nodes + that were connected through this edge. Parameters ---------- @@ -2446,6 +3030,10 @@ def qr(edge: Edge) -> Tuple[Node, Node]: Contracts an edge via :func:`contract` and splits it via :func:`split` using ``mode = "qr"``. See :func:`split` for a more complete explanation. + This only works if the nodes connected through the edge are ``leaf`` nodes. + Otherwise, this will perform the contraction between the ``leaf`` nodes + that were connected through this edge. + This operation is the same as :meth:`~Edge.qr`. Parameters @@ -2547,6 +3135,10 @@ def qr(edge: Edge) -> Tuple[Node, Node]: Contracts an edge via :meth:`~Edge.contract` and splits it via :meth:`~AbstractNode.split` using ``mode = "qr"``. See :func:`split` for a more complete explanation. + + This only works if the nodes connected through the edge are ``leaf`` nodes. + Otherwise, this will perform the contraction between the ``leaf`` nodes + that were connected through this edge. Returns ------- @@ -2745,6 +3337,10 @@ def rq(edge: Edge) -> Tuple[Node, Node]: Contracts an edge via :func:`contract` and splits it via :func:`split` using ``mode = "rq"``. See :func:`split` for a more complete explanation. + This only works if the nodes connected through the edge are ``leaf`` nodes. + Otherwise, this will perform the contraction between the ``leaf`` nodes + that were connected through this edge. + This operation is the same as :meth:`~Edge.rq`. Parameters @@ -2846,6 +3442,10 @@ def rq(edge: Edge) -> Tuple[Node, Node]: Contracts an edge via :meth:`~Edge.contract` and splits it via :meth:`~AbstractNode.split` using ``mode = "rq"``. See :func:`split` for a more complete explanation. + + This only works if the nodes connected through the edge are ``leaf`` nodes. + Otherwise, this will perform the contraction between the ``leaf`` nodes + that were connected through this edge. Returns ------- @@ -3397,6 +3997,10 @@ def contract(edge: Edge) -> Node: """ Contracts the nodes that are connected through the edge. + This only works if the nodes connected through the edge are ``leaf`` nodes. + Otherwise, this will perform the contraction between the ``leaf`` nodes + that were connected through this edge. + Nodes ``resultant`` from this operation are called ``"contract_edges"``. The node that keeps information about the :class:`Successor` is ``edge.node1``. @@ -3436,6 +4040,10 @@ def contract(edge: Edge) -> Node: """ Contracts the nodes that are connected through the edge. + This only works if the nodes connected through the edge are ``leaf`` nodes. + Otherwise, this will perform the contraction between the ``leaf`` nodes + that were connected through this edge. + Nodes ``resultant`` from this operation are called ``"contract_edges"``. The node that keeps information about the :class:`Successor` is ``self.node1``. @@ -4031,6 +4639,13 @@ def stack(nodes: Sequence[AbstractNode]): :meth:`~TensorNetwork.auto_stack` mode affects the computation of :func:`stack`. + If this operation is used several times with the same input nodes, but their + dimensions can change from one call to another, this will lead to undesired + behaviour. The network should be :meth:`~tensorkrwoch.TensorNetwork.reset`. + This situation should be avoided in the + :meth:`~tensorkrowch.TensorNetwork.contract` method. Otherwise it will fail + in subsequent calls to ``contract`` or :meth:`~tensorkrowch.TensorNetwork.forward` + Nodes ``resultant`` from this operation are called ``"stack"``. If this operation returns a ``virtual`` :class:`ParamStackNode`, it will be called ``"virtual_result_stack"``. See :class:AbstractNode` to learn about this @@ -4569,7 +5184,7 @@ def _einsum_next(successor: Successor, def einsum(string: Text, *nodes: Sequence[AbstractNode]) -> Node: - """ + r""" Performs einsum contraction based on `opt_einsum `_. This operation facilitates contracting several nodes at once, specifying @@ -4634,7 +5249,7 @@ def einsum(string: Text, *nodes: Sequence[AbstractNode]) -> Node: ############################## STACKED EINSUM ############################# def stacked_einsum(string: Text, *nodes_lists: List[AbstractNode]) -> List[Node]: - """ + r""" Applies the same :func:`einsum` operation (same ``string``) to a sequence of groups of nodes (all groups having the same amount of nodes, with the same properties, etc.). That is, it stacks these groups of nodes into a diff --git a/tests/decompositions/test_svd_decompositions.py b/tests/decompositions/test_svd_decompositions.py index 4a6b59b..c454602 100644 --- a/tests/decompositions/test_svd_decompositions.py +++ b/tests/decompositions/test_svd_decompositions.py @@ -13,65 +13,182 @@ class TestSVDDecompositions: def test_vec_to_mps(self): - for i in range(1, 6): - dims = torch.randint(low=2, high=10, size=(i,)) - vec = torch.randn(*dims) * 1e-5 - for j in range(i + 1): - tensors = tk.decompositions.vec_to_mps(vec=vec, - n_batches=j, - rank=5) + for renormalize in [True, False]: + for i in range(1, 6): + dims = torch.randint(low=2, high=10, size=(i,)) + vec = torch.randn(*dims) * 1e-5 + for j in range(i + 1): + tensors = tk.decompositions.vec_to_mps(vec=vec, + n_batches=j, + rank=5, + renormalize=renormalize) + + for k, tensor in enumerate(tensors): + assert tensor.shape[:j] == vec.shape[:j] + if k == 0: + if j < i: + assert tensor.shape[j] == vec.shape[j] + if j < i - 1: + assert tensor.shape[j + 1] <= 5 + elif k < (i - j - 1): + assert tensor.shape[j + 1] == vec.shape[j + k] + assert tensor.shape[j + 2] <= 5 + else: + assert tensor.shape[j + 1] == vec.shape[j + k] + + bond_dims = [tensor.shape[-1] for tensor in tensors[:-1]] + + if i - j > 0: + mps = tk.models.MPSData(tensors=tensors, + n_batches=j) + assert mps.phys_dim == dims[j:].tolist() + assert mps.bond_dim == bond_dims + + if j == 0: + mps = tk.models.MPS(tensors=tensors) + assert mps.phys_dim == dims.tolist() + assert mps.bond_dim == bond_dims + + def test_vec_to_mps_accuracy(self): + for renormalize in [True, False]: + for i in range(1, 6): + dims = torch.randint(low=2, high=10, size=(i,)) + vec = torch.randn(*dims) * 1e-1 + for j in range(i + 1): + tensors = tk.decompositions.vec_to_mps(vec=vec, + n_batches=j, + cum_percentage=0.9999, + renormalize=renormalize) + + bond_dims = [tensor.shape[-1] for tensor in tensors[:-1]] + + if i - j > 0: + mps = tk.models.MPSData(tensors=tensors, + n_batches=j) + assert mps.phys_dim == dims[j:].tolist() + assert mps.bond_dim == bond_dims + + if j == 0: + mps = tk.models.MPS(tensors=tensors) + assert mps.phys_dim == dims.tolist() + assert mps.bond_dim == bond_dims + + approx_vec = mps.left_node + for node in mps.mats_env + [mps.right_node]: + approx_vec @= node + + approx_vec = approx_vec.tensor + diff = vec - approx_vec + assert diff.norm() < 1e-1 + + def test_mat_to_mpo(self): + for renormalize in [True, False]: + for i in range(1, 6): + dims = torch.randint(low=2, high=10, size=(2 * i,)) + mat = torch.randn(*dims) * 1e-5 + + tensors = tk.decompositions.mat_to_mpo(mat=mat, + rank=5, + renormalize=renormalize) for k, tensor in enumerate(tensors): - assert tensor.shape[:j] == vec.shape[:j] if k == 0: - if j < i: - assert tensor.shape[j] == vec.shape[j] - if j < i - 1: - assert tensor.shape[j + 1] <= 5 - elif k < (i - j - 1): - assert tensor.shape[j + 1] == vec.shape[j + k] - assert tensor.shape[j + 2] <= 5 + assert tensor.shape[0] == dims[2 * k] + if i > 1: + assert tensor.shape[1] <= 5 + assert tensor.shape[-1] == dims[2 * k + 1] + elif k < (i - 1): + assert tensor.shape[1] == dims[2 * k] + assert tensor.shape[2] <= 5 + assert tensor.shape[3] == dims[2 * k + 1] else: - assert tensor.shape[j + 1] == vec.shape[j + k] + assert tensor.shape[-2] == dims[2 * k] + assert tensor.shape[-1] == dims[2 * k + 1] + + bond_dims = [tensor.shape[-2] for tensor in tensors[:-1]] + + mpo = tk.models.MPO(tensors=tensors) + assert mpo.in_dim == [dims[2 * j] for j in range(i)] + assert mpo.out_dim == [dims[2 * j + 1] for j in range(i)] + assert mpo.bond_dim == bond_dims + + def test_mat_to_mpo_accuracy(self): + for renormalize in [True, False]: + for i in range(1, 6): + dims = torch.randint(low=2, high=10, size=(2 * i,)) + mat = torch.randn(*dims) * 1e-1 + + tensors = tk.decompositions.mat_to_mpo(mat=mat, + cum_percentage=0.9999, + renormalize=renormalize) + bond_dims = [tensor.shape[-2] for tensor in tensors[:-1]] - bond_dims = [tensor.shape[-1] for tensor in tensors[:-1]] + mpo = tk.models.MPO(tensors=tensors) + assert mpo.in_dim == [dims[2 * j] for j in range(i)] + assert mpo.out_dim == [dims[2 * j + 1] for j in range(i)] + assert mpo.bond_dim == bond_dims - if i - j > 0: - mps = tk.models.MPSData(tensors=tensors, - n_batches=j) - assert mps.phys_dim == dims[j:].tolist() - assert mps.bond_dim == bond_dims + approx_mat = mpo.left_node + for node in mpo.mats_env + [mpo.right_node]: + approx_mat @= node - if j == 0: - mps = tk.models.MPS(tensors=tensors) - assert mps.phys_dim == dims.tolist() - assert mps.bond_dim == bond_dims + approx_mat = approx_mat.tensor + diff = mat - approx_mat + assert diff.norm() < 1e-1 - def test_mat_to_mpo(self): - for i in range(1, 6): - dims = torch.randint(low=2, high=10, size=(2 * i,)) - mat = torch.randn(*dims) * 1e-5 + def test_mat_to_mpo_permuted_accuracy(self): + for renormalize in [True, False]: + dims = [2, 2, 2, 2, 3, 3, 3, 3] # inputs (2) x outputs (3) + mat = torch.randn(*dims) * 1e-1 + aux_mat = mat.permute(0, 4, 1, 5, 2, 6, 3, 7) + assert aux_mat.shape == (2, 3, 2, 3, 2, 3, 2, 3) - tensors = tk.decompositions.mat_to_mpo(mat=mat, rank=5) + tensors = tk.decompositions.mat_to_mpo(mat=aux_mat, + cum_percentage=0.9999, + renormalize=renormalize) + bond_dims = [tensor.shape[-2] for tensor in tensors[:-1]] + + mpo = tk.models.MPO(tensors=tensors) + assert mpo.in_dim == [2] * 4 + assert mpo.out_dim == [3] * 4 + assert mpo.bond_dim == bond_dims - for k, tensor in enumerate(tensors): - if k == 0: - assert tensor.shape[0] == dims[2 * k] - if i > 1: - assert tensor.shape[1] <= 5 - assert tensor.shape[-1] == dims[2 * k + 1] - elif k < (i - 1): - assert tensor.shape[1] == dims[2 * k] - assert tensor.shape[2] <= 5 - assert tensor.shape[3] == dims[2 * k + 1] - else: - assert tensor.shape[-2] == dims[2 * k] - assert tensor.shape[-1] == dims[2 * k + 1] + approx_mat = mpo.left_node + for node in mpo.mats_env + [mpo.right_node]: + approx_mat @= node + assert approx_mat.shape == (2, 3, 2, 3, 2, 3, 2, 3) + + approx_mat = approx_mat.tensor + approx_mat = approx_mat.permute(0, 2, 4, 6, 1, 3, 5, 7) + diff = mat - approx_mat + assert diff.norm() < 1e-1 + + def test_mat_to_mpo_permuted_diff_dimsaccuracy(self): + for renormalize in [True, False]: + # inputs (2, 3, 4, 2) x outputs (3, 5, 7, 2) + dims = [2, 3, 4, 2, 3, 5, 7, 2] + mat = torch.randn(*dims) * 1e-1 + aux_mat = mat.permute(0, 4, 1, 5, 2, 6, 3, 7) + assert aux_mat.shape == (2, 3, 3, 5, 4, 7, 2, 2) + + tensors = tk.decompositions.mat_to_mpo(mat=aux_mat, + cum_percentage=0.9999, + renormalize=renormalize) bond_dims = [tensor.shape[-2] for tensor in tensors[:-1]] mpo = tk.models.MPO(tensors=tensors) - assert mpo.in_dim == [dims[2 * j] for j in range(i)] - assert mpo.out_dim == [dims[2 * j + 1] for j in range(i)] + assert mpo.in_dim == [2, 3, 4, 2] + assert mpo.out_dim == [3, 5, 7, 2] assert mpo.bond_dim == bond_dims - \ No newline at end of file + + approx_mat = mpo.left_node + for node in mpo.mats_env + [mpo.right_node]: + approx_mat @= node + + assert approx_mat.shape == (2, 3, 3, 5, 4, 7, 2, 2) + + approx_mat = approx_mat.tensor + approx_mat = approx_mat.permute(0, 2, 4, 6, 1, 3, 5, 7) + diff = mat - approx_mat + assert diff.norm() < 1e-1 diff --git a/tests/models/test_mpo.py b/tests/models/test_mpo.py index 76915b2..3d4b7d6 100644 --- a/tests/models/test_mpo.py +++ b/tests/models/test_mpo.py @@ -334,31 +334,34 @@ def test_all_algorithms(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mpo.auto_stack = auto_stack - mpo.auto_unbind = auto_unbind + for renormalize in [True, False]: + mpo.auto_stack = auto_stack + mpo.auto_unbind = auto_unbind - mpo.trace(example, - inline_input=inline_input, - inline_mats=inline_mats) - result = mpo(data, - inline_input=inline_input, - inline_mats=inline_mats) + mpo.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) + result = mpo(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) - assert result.shape == tuple([100] + [2] * n_features) - assert len(mpo.edges) == n_features - if boundary == 'obc': - assert len(mpo.leaf_nodes) == n_features + 2 - else: - assert len(mpo.leaf_nodes) == n_features - assert len(mpo.data_nodes) == n_features - if not inline_input and auto_stack: - assert len(mpo.virtual_nodes) == 2 - else: - assert len(mpo.virtual_nodes) == 1 - - result.sum().backward() - for node in mpo.mats_env: - assert node.grad is not None + assert result.shape == tuple([100] + [2] * n_features) + assert len(mpo.edges) == n_features + if boundary == 'obc': + assert len(mpo.leaf_nodes) == n_features + 2 + else: + assert len(mpo.leaf_nodes) == n_features + assert len(mpo.data_nodes) == n_features + if not inline_input and auto_stack: + assert len(mpo.virtual_nodes) == 2 + else: + assert len(mpo.virtual_nodes) == 1 + + result.sum().backward() + for node in mpo.mats_env: + assert node.grad is not None def test_all_algorithms_cuda(self): device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') @@ -379,31 +382,34 @@ def test_all_algorithms_cuda(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mpo.auto_stack = auto_stack - mpo.auto_unbind = auto_unbind + for renormalize in [True, False]: + mpo.auto_stack = auto_stack + mpo.auto_unbind = auto_unbind - mpo.trace(example, - inline_input=inline_input, - inline_mats=inline_mats) - result = mpo(data, - inline_input=inline_input, - inline_mats=inline_mats) + mpo.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) + result = mpo(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) - assert result.shape == tuple([100] + [2] * n_features) - assert len(mpo.edges) == n_features - if boundary == 'obc': - assert len(mpo.leaf_nodes) == n_features + 2 - else: - assert len(mpo.leaf_nodes) == n_features - assert len(mpo.data_nodes) == n_features - if not inline_input and auto_stack: - assert len(mpo.virtual_nodes) == 2 - else: - assert len(mpo.virtual_nodes) == 1 - - result.sum().backward() - for node in mpo.mats_env: - assert node.grad is not None + assert result.shape == tuple([100] + [2] * n_features) + assert len(mpo.edges) == n_features + if boundary == 'obc': + assert len(mpo.leaf_nodes) == n_features + 2 + else: + assert len(mpo.leaf_nodes) == n_features + assert len(mpo.data_nodes) == n_features + if not inline_input and auto_stack: + assert len(mpo.virtual_nodes) == 2 + else: + assert len(mpo.virtual_nodes) == 1 + + result.sum().backward() + for node in mpo.mats_env: + assert node.grad is not None def test_mpo_mps_data_manually(self): mpo = tk.models.MPO(n_features=10, @@ -452,62 +458,135 @@ def test_mpo_mps_data_all_algorithms(self): for mps_boundary in ['obc', 'pbc']: for inline_input in [True, False]: for inline_mats in [True, False]: - phys_dim = torch.randint(low=2, high=10, - size=(n_features,)).tolist() - bond_dim = torch.randint(low=2, high=8, - size=(n_features,)).tolist() - + for renormalize in [True, False]: + phys_dim = torch.randint(low=2, high=10, + size=(n_features,)).tolist() + bond_dim = torch.randint(low=2, high=8, + size=(n_features,)).tolist() + + mpo = tk.models.MPO( + n_features=n_features, + in_dim=phys_dim, + out_dim=2, + bond_dim=10, + boundary=mpo_boundary) + + mps_data = tk.models.MPSData( + n_features=n_features, + phys_dim=phys_dim, + bond_dim=bond_dim[:-1] \ + if mps_boundary == 'obc' else bond_dim, + boundary=mps_boundary) + + for _ in range(10): + # MPO needs to be reset each time if inline_input + # or inline_mats are False, since the bond dims + # of MPSData may change, and therefore the + # computation of stacks may fail + if not inline_input or not inline_mats: + mpo.reset() + + bond_dim = torch.randint(low=2, high=8, + size=(n_features,)).tolist() + tensors = [ + torch.randn(10, + bond_dim[i - 1], + phys_dim[i], + bond_dim[i]) + for i in range(n_features)] + if mps_boundary == 'obc': + tensors[0] = tensors[0][:, 0] + tensors[-1] = tensors[-1][..., 0] + + mps_data.add_data(tensors) + result = mpo(mps=mps_data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) + + assert result.shape == tuple([10] + [2] * n_features) + + for i, node in enumerate(mpo.mats_env): + assert node.is_connected_to(mps_data.mats_env[i]) + + # Check if unset_data_nodes removes MPSData nodes + # from MPO + mpo.unset_data_nodes() + + for i, node in enumerate(mpo.mats_env): + assert not node.is_connected_to(mps_data.mats_env[i]) + assert mps_data.mats_env[i].network is None + + def test_canonicalize(self): + for n_features in [1, 2, 3, 4, 10]: + for boundary in ['obc', 'pbc']: + for oc in range(n_features): + for mode in ['svd', 'svdr', 'qr']: + for renormalize in [True, False]: mpo = tk.models.MPO( n_features=n_features, - in_dim=phys_dim, + in_dim=2, out_dim=2, bond_dim=10, - boundary=mpo_boundary) + boundary=boundary) - mps_data = tk.models.MPSData( - n_features=n_features, - phys_dim=phys_dim, - bond_dim=bond_dim[:-1] \ - if mps_boundary == 'obc' else bond_dim, - boundary=mps_boundary) + # Canonicalize + rank = torch.randint(5, 11, (1,)).item() + mpo.canonicalize(oc=oc, + mode=mode, + rank=rank, + cum_percentage=0.98, + cutoff=1e-5, + renormalize=renormalize) - for _ in range(10): - # MPO needs to be reset each time if inline_input - # or inline_mats are False, since the bond dims - # of MPSData may change, and therefore the - # computation of stacks may fail - if not inline_input or not inline_mats: - mpo.reset() - - bond_dim = torch.randint(low=2, high=8, - size=(n_features,)).tolist() - tensors = [ - torch.randn(10, - bond_dim[i - 1], - phys_dim[i], - bond_dim[i]) - for i in range(n_features)] - if mps_boundary == 'obc': - tensors[0] = tensors[0][:, 0] - tensors[-1] = tensors[-1][..., 0] - - mps_data.add_data(tensors) - result = mpo(mps=mps_data, - inline_input=inline_input, - inline_mats=inline_mats) + if mpo.bond_dim and mode != 'qr': + if mpo.boundary == 'obc': + assert (torch.tensor(mpo.bond_dim) <= rank).all() + else: + assert (torch.tensor(mpo.bond_dim[:-1]) <= rank).all() + + if mpo.boundary == 'obc': + assert len(mpo.leaf_nodes) == n_features + 2 + else: + assert len(mpo.leaf_nodes) == n_features + + def test_canonicalize_diff_bond_dims(self): + for n_features in [1, 2, 3, 4, 10]: + for boundary in ['obc', 'pbc']: + for oc in range(n_features): + for mode in ['svd', 'svdr', 'qr']: + for renormalize in [True, False]: + bond_dim = torch.randint(low=2, high=10, + size=(n_features,)).tolist() + bond_dim = bond_dim[:-1] if boundary == 'obc' \ + else bond_dim - assert result.shape == tuple([10] + [2] * n_features) + mpo = tk.models.MPO( + n_features=n_features, + in_dim=2, + out_dim=2, + bond_dim=bond_dim, + boundary=boundary) - for i, node in enumerate(mpo.mats_env): - assert node.is_connected_to(mps_data.mats_env[i]) + # Canonicalize + rank = torch.randint(5, 11, (1,)).item() + mpo.canonicalize(oc=oc, + mode=mode, + rank=rank, + cum_percentage=0.98, + cutoff=1e-5, + renormalize=renormalize) - # Check if unset_data_nodes removes MPSData nodes - # from MPO - mpo.unset_data_nodes() + if mpo.bond_dim and mode != 'qr': + if mpo.boundary == 'obc': + assert (torch.tensor(mpo.bond_dim) <= rank).all() + else: + assert (torch.tensor(mpo.bond_dim[:-1]) <= rank).all() - for i, node in enumerate(mpo.mats_env): - assert not node.is_connected_to(mps_data.mats_env[i]) - assert mps_data.mats_env[i].network is None + if mpo.boundary == 'obc': + assert len(mpo.leaf_nodes) == n_features + 2 + else: + assert len(mpo.leaf_nodes) == n_features class TestUMPO: diff --git a/tests/models/test_mps.py b/tests/models/test_mps.py index 28b0ab5..50feb08 100644 --- a/tests/models/test_mps.py +++ b/tests/models/test_mps.py @@ -96,7 +96,8 @@ def test_initialize_with_tensors_errors(self): mps = tk.models.MPS(tensors=tensors) def test_initialize_init_method(self): - methods = ['zeros', 'ones', 'copy', 'rand', 'randn', 'randn_eye'] + methods = ['zeros', 'ones', 'copy', 'rand', 'randn', + 'randn_eye', 'unit', 'canonical'] for n in [1, 2, 5]: for init_method in methods: # PBC @@ -133,7 +134,8 @@ def test_initialize_init_method(self): def test_initialize_init_method_cuda(self): device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') - methods = ['zeros', 'ones', 'copy', 'rand', 'randn', 'randn_eye'] + methods = ['zeros', 'ones', 'copy', 'rand', 'randn', + 'randn_eye', 'unit', 'canonical'] for n in [1, 2, 5]: for init_method in methods: # PBC @@ -174,47 +176,46 @@ def test_initialize_init_method_cuda(self): assert torch.equal(mps.right_node.tensor[1:], torch.zeros_like(mps.right_node.tensor)[1:]) - def test_initialize_with_unitaries(self): + def test_initialize_canonical(self): for n in [1, 2, 5]: # PBC mps = tk.models.MPS(boundary='pbc', n_features=n, phys_dim=2, bond_dim=2, - init_method='unit') + init_method='canonical') assert mps.n_features == n assert mps.boundary == 'pbc' assert mps.phys_dim == [2] * n assert mps.bond_dim == [2] * n - # For PBC norm does not have to be 1. always + # For PBC norm does not have to be 2**n always # OBC mps = tk.models.MPS(boundary='obc', n_features=n, phys_dim=2, bond_dim=2, - init_method='unit') + init_method='canonical') assert mps.n_features == n assert mps.boundary == 'obc' assert mps.phys_dim == [2] * n assert mps.bond_dim == [2] * (n - 1) - # Check it has norm == 1 - assert mps.norm().isclose(torch.tensor(1.)) - # Norm is close to 1. if bond dimension is <= than - # physical dimension, otherwise, it will not be exactly 1. + # Check it has norm == 2**n + assert mps.norm().isclose(torch.tensor(2. ** n).sqrt()) + # Norm is close to 2**n if bond dimension is <= than + # physical dimension, otherwise, it will not be exactly 2**n - def test_initialize_with_unitaries_cuda(self): + def test_initialize_canonical_cuda(self): device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') - for n in [1, 2, 5]: # PBC mps = tk.models.MPS(boundary='pbc', n_features=n, phys_dim=2, bond_dim=2, - init_method='unit', + init_method='canonical', device=device) assert mps.n_features == n assert mps.boundary == 'pbc' @@ -223,14 +224,14 @@ def test_initialize_with_unitaries_cuda(self): for node in mps.mats_env: assert node.device == device - # For PBC norm does not have to be 1. always + # For PBC norm does not have to be 2**n always # OBC mps = tk.models.MPS(boundary='obc', n_features=n, phys_dim=2, bond_dim=2, - init_method='unit', + init_method='canonical', device=device) assert mps.n_features == n assert mps.boundary == 'obc' @@ -239,10 +240,11 @@ def test_initialize_with_unitaries_cuda(self): for node in mps.mats_env: assert node.device == device - # Check it has norm == 1 - assert mps.norm().isclose(torch.tensor(1.)) - # Norm is close to 1. if bond dimension is <= than - # physical dimension, otherwise, it will not be exactly 1. + # Check it has norm == 2**n + norm = mps.norm() + assert mps.norm().isclose(torch.tensor(2. ** n).sqrt()) + # Norm is close to 2**n if bond dimension is <= than + # physical dimension, otherwise, it will not be exactly 2**n def test_in_and_out_features(self): tensors = [torch.randn(10, 2, 10) for _ in range(10)] @@ -401,31 +403,34 @@ def test_all_algorithms(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats) + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) - assert result.shape == (100,) - assert len(mps.edges) == 0 - if boundary == 'obc': - assert len(mps.leaf_nodes) == n_features + 2 - else: - assert len(mps.leaf_nodes) == n_features - assert len(mps.data_nodes) == n_features - if not inline_input and auto_stack: - assert len(mps.virtual_nodes) == 2 - else: - assert len(mps.virtual_nodes) == 1 - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None + assert result.shape == (100,) + assert len(mps.edges) == 0 + if boundary == 'obc': + assert len(mps.leaf_nodes) == n_features + 2 + else: + assert len(mps.leaf_nodes) == n_features + assert len(mps.data_nodes) == n_features + if not inline_input and auto_stack: + assert len(mps.virtual_nodes) == 2 + else: + assert len(mps.virtual_nodes) == 1 + + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None def test_all_algorithms_cuda(self): device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') @@ -445,31 +450,34 @@ def test_all_algorithms_cuda(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats) + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) - assert result.shape == (100,) - assert len(mps.edges) == 0 - if boundary == 'obc': - assert len(mps.leaf_nodes) == n_features + 2 - else: - assert len(mps.leaf_nodes) == n_features - assert len(mps.data_nodes) == n_features - if not inline_input and auto_stack: - assert len(mps.virtual_nodes) == 2 - else: - assert len(mps.virtual_nodes) == 1 - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None + assert result.shape == (100,) + assert len(mps.edges) == 0 + if boundary == 'obc': + assert len(mps.leaf_nodes) == n_features + 2 + else: + assert len(mps.leaf_nodes) == n_features + assert len(mps.data_nodes) == n_features + if not inline_input and auto_stack: + assert len(mps.virtual_nodes) == 2 + else: + assert len(mps.virtual_nodes) == 1 + + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None def test_all_algorithms_diff_in_dim(self): for n_features in [1, 2, 3, 4, 6]: @@ -494,37 +502,40 @@ def test_all_algorithms_diff_in_dim(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats) + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) - assert result.shape == (100,) - assert len(mps.edges) == 0 - if boundary == 'obc': - assert len(mps.leaf_nodes) == n_features + 2 - else: - assert len(mps.leaf_nodes) == n_features - assert len(mps.data_nodes) == n_features - if not inline_input and auto_stack: - if n_features == 1: - assert len(mps.virtual_nodes) == 2 + assert result.shape == (100,) + assert len(mps.edges) == 0 + if boundary == 'obc': + assert len(mps.leaf_nodes) == n_features + 2 else: - assert len(mps.virtual_nodes) == 1 - else: - if n_features == 1: - assert len(mps.virtual_nodes) == 1 + assert len(mps.leaf_nodes) == n_features + assert len(mps.data_nodes) == n_features + if not inline_input and auto_stack: + if n_features == 1: + assert len(mps.virtual_nodes) == 2 + else: + assert len(mps.virtual_nodes) == 1 else: - assert len(mps.virtual_nodes) == 0 - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None + if n_features == 1: + assert len(mps.virtual_nodes) == 1 + else: + assert len(mps.virtual_nodes) == 0 + + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None def test_all_algorithms_diff_bond_dim(self): for n_features in [1, 2, 3, 4, 6]: @@ -543,31 +554,34 @@ def test_all_algorithms_diff_bond_dim(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats) + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) - assert result.shape == (100,) - assert len(mps.edges) == 0 - if boundary == 'obc': - assert len(mps.leaf_nodes) == n_features + 2 - else: - assert len(mps.leaf_nodes) == n_features - assert len(mps.data_nodes) == n_features - if not inline_input and auto_stack: - assert len(mps.virtual_nodes) == 2 - else: - assert len(mps.virtual_nodes) == 1 - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None + assert result.shape == (100,) + assert len(mps.edges) == 0 + if boundary == 'obc': + assert len(mps.leaf_nodes) == n_features + 2 + else: + assert len(mps.leaf_nodes) == n_features + assert len(mps.data_nodes) == n_features + if not inline_input and auto_stack: + assert len(mps.virtual_nodes) == 2 + else: + assert len(mps.virtual_nodes) == 1 + + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None def test_all_algorithms_diff_in_dim_bond_dim(self): for n_features in [1, 2, 3, 4, 6]: @@ -594,40 +608,43 @@ def test_all_algorithms_diff_in_dim_bond_dim(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats) + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) - assert result.shape == (100,) - assert len(mps.edges) == 0 - - if boundary == 'obc': - assert len(mps.leaf_nodes) == n_features + 2 - else: - assert len(mps.leaf_nodes) == n_features + assert result.shape == (100,) + assert len(mps.edges) == 0 - assert len(mps.data_nodes) == n_features - - if not inline_input and auto_stack: - if n_features == 1: - assert len(mps.virtual_nodes) == 2 + if boundary == 'obc': + assert len(mps.leaf_nodes) == n_features + 2 else: - assert len(mps.virtual_nodes) == 1 - else: - if n_features == 1: - assert len(mps.virtual_nodes) == 1 + assert len(mps.leaf_nodes) == n_features + + assert len(mps.data_nodes) == n_features + + if not inline_input and auto_stack: + if n_features == 1: + assert len(mps.virtual_nodes) == 2 + else: + assert len(mps.virtual_nodes) == 1 else: - assert len(mps.virtual_nodes) == 0 - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None + if n_features == 1: + assert len(mps.virtual_nodes) == 1 + else: + assert len(mps.virtual_nodes) == 0 + + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None def test_all_algorithms_marginalize(self): for n_features in [1, 2, 3, 4, 10]: @@ -654,43 +671,46 @@ def test_all_algorithms_marginalize(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True) + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True) - if in_features: - assert result.shape == (100, 100) - - if not inline_input and auto_stack: - assert len(mps.virtual_nodes) == \ - (2 + len(mps.out_features)) + if in_features: + assert result.shape == (100, 100) + + if not inline_input and auto_stack: + assert len(mps.virtual_nodes) == \ + (2 + len(mps.out_features)) + else: + assert len(mps.virtual_nodes) == \ + (1 + len(mps.out_features)) + else: + assert result.shape == tuple() assert len(mps.virtual_nodes) == \ - (1 + len(mps.out_features)) + len(mps.out_features) + + if boundary == 'obc': + assert len(mps.leaf_nodes) == n_features + 2 + else: + assert len(mps.leaf_nodes) == n_features - else: - assert result.shape == tuple() - assert len(mps.virtual_nodes) == \ - len(mps.out_features) - - if boundary == 'obc': - assert len(mps.leaf_nodes) == n_features + 2 - else: - assert len(mps.leaf_nodes) == n_features + assert len(mps.data_nodes) == len(in_features) - assert len(mps.data_nodes) == len(in_features) - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None def test_all_algorithms_marginalize_with_list_matrices(self): for n_features in [1, 2, 3, 4, 10]: @@ -720,45 +740,48 @@ def test_all_algorithms_marginalize_with_list_matrices(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True, - embedding_matrices=embedding_matrices) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True, - embedding_matrices=embedding_matrices) + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True, + embedding_matrices=embedding_matrices) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True, + embedding_matrices=embedding_matrices) - if in_features: - assert result.shape == (100, 100) - - if not inline_input and auto_stack: - assert len(mps.virtual_nodes) == \ - (2 + 2 * len(mps.out_features)) + if in_features: + assert result.shape == (100, 100) + + if not inline_input and auto_stack: + assert len(mps.virtual_nodes) == \ + (2 + 2 * len(mps.out_features)) + else: + assert len(mps.virtual_nodes) == \ + (1 + 2 * len(mps.out_features)) + else: + assert result.shape == tuple() assert len(mps.virtual_nodes) == \ - (1 + 2 * len(mps.out_features)) + 2 * len(mps.out_features) + + if boundary == 'obc': + assert len(mps.leaf_nodes) == n_features + 2 + else: + assert len(mps.leaf_nodes) == n_features - else: - assert result.shape == tuple() - assert len(mps.virtual_nodes) == \ - 2 * len(mps.out_features) - - if boundary == 'obc': - assert len(mps.leaf_nodes) == n_features + 2 - else: - assert len(mps.leaf_nodes) == n_features + assert len(mps.data_nodes) == len(in_features) - assert len(mps.data_nodes) == len(in_features) - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None def test_all_algorithms_marginalize_with_matrix(self): for n_features in [1, 2, 3, 4, 10]: @@ -786,45 +809,48 @@ def test_all_algorithms_marginalize_with_matrix(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True, - embedding_matrices=embedding_matrix) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True, - embedding_matrices=embedding_matrix) + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True, + embedding_matrices=embedding_matrix) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True, + embedding_matrices=embedding_matrix) - if in_features: - assert result.shape == (100, 100) - - if not inline_input and auto_stack: - assert len(mps.virtual_nodes) == \ - (2 + 2 * len(mps.out_features)) + if in_features: + assert result.shape == (100, 100) + + if not inline_input and auto_stack: + assert len(mps.virtual_nodes) == \ + (2 + 2 * len(mps.out_features)) + else: + assert len(mps.virtual_nodes) == \ + (1 + 2 * len(mps.out_features)) + else: + assert result.shape == tuple() assert len(mps.virtual_nodes) == \ - (1 + 2 * len(mps.out_features)) + 2 * len(mps.out_features) + + if boundary == 'obc': + assert len(mps.leaf_nodes) == n_features + 2 + else: + assert len(mps.leaf_nodes) == n_features - else: - assert result.shape == tuple() - assert len(mps.virtual_nodes) == \ - 2 * len(mps.out_features) - - if boundary == 'obc': - assert len(mps.leaf_nodes) == n_features + 2 - else: - assert len(mps.leaf_nodes) == n_features + assert len(mps.data_nodes) == len(in_features) - assert len(mps.data_nodes) == len(in_features) - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None def test_all_algorithms_marginalize_with_matrix_cuda(self): device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') @@ -855,45 +881,48 @@ def test_all_algorithms_marginalize_with_matrix_cuda(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True, - embedding_matrices=embedding_matrix) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True, - embedding_matrices=embedding_matrix) + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True, + embedding_matrices=embedding_matrix) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True, + embedding_matrices=embedding_matrix) - if in_features: - assert result.shape == (100, 100) - - if not inline_input and auto_stack: - assert len(mps.virtual_nodes) == \ - (2 + 2 * len(mps.out_features)) + if in_features: + assert result.shape == (100, 100) + + if not inline_input and auto_stack: + assert len(mps.virtual_nodes) == \ + (2 + 2 * len(mps.out_features)) + else: + assert len(mps.virtual_nodes) == \ + (1 + 2 * len(mps.out_features)) + else: + assert result.shape == tuple() assert len(mps.virtual_nodes) == \ - (1 + 2 * len(mps.out_features)) + 2 * len(mps.out_features) + + if boundary == 'obc': + assert len(mps.leaf_nodes) == n_features + 2 + else: + assert len(mps.leaf_nodes) == n_features - else: - assert result.shape == tuple() - assert len(mps.virtual_nodes) == \ - 2 * len(mps.out_features) - - if boundary == 'obc': - assert len(mps.leaf_nodes) == n_features + 2 - else: - assert len(mps.leaf_nodes) == n_features + assert len(mps.data_nodes) == len(in_features) - assert len(mps.data_nodes) == len(in_features) - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None def test_all_algorithms_marginalize_with_mpo(self): for n_features in [1, 2, 3, 4, 10]: @@ -930,49 +959,52 @@ def test_all_algorithms_marginalize_with_mpo(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind - - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True, - mpo=mpo) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True, - mpo=mpo) - - if in_features: - assert result.shape == (100, 100) - else: - assert result.shape == tuple() - - if mps_boundary == 'obc': - if mpo_boundary == 'obc': - leaf = (n_features + 2) + \ - (n_features - len(in_features) + 2) - assert len(mps.leaf_nodes) == leaf + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind + + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True, + mpo=mpo) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True, + mpo=mpo) + + if in_features: + assert result.shape == (100, 100) else: - leaf = (n_features + 2) + \ - (n_features - len(in_features)) - assert len(mps.leaf_nodes) == leaf - else: - if mpo_boundary == 'obc': - leaf = n_features + \ - (n_features - len(in_features) + 2) - assert len(mps.leaf_nodes) == leaf + assert result.shape == tuple() + + if mps_boundary == 'obc': + if mpo_boundary == 'obc': + leaf = (n_features + 2) + \ + (n_features - len(in_features) + 2) + assert len(mps.leaf_nodes) == leaf + else: + leaf = (n_features + 2) + \ + (n_features - len(in_features)) + assert len(mps.leaf_nodes) == leaf else: - leaf = n_features + \ - (n_features - len(in_features)) - assert len(mps.leaf_nodes) == leaf - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None - for node in mpo.mats_env: - assert node.tensor.grad is None + if mpo_boundary == 'obc': + leaf = n_features + \ + (n_features - len(in_features) + 2) + assert len(mps.leaf_nodes) == leaf + else: + leaf = n_features + \ + (n_features - len(in_features)) + assert len(mps.leaf_nodes) == leaf + + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None + for node in mpo.mats_env: + assert node.tensor.grad is None def test_all_algorithms_marginalize_with_mpo_cuda(self): device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') @@ -1017,49 +1049,52 @@ def test_all_algorithms_marginalize_with_mpo_cuda(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind - - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True, - mpo=mpo) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True, - mpo=mpo) - - if in_features: - assert result.shape == (100, 100) - else: - assert result.shape == tuple() - - if mps_boundary == 'obc': - if mpo_boundary == 'obc': - leaf = (n_features + 2) + \ - (n_features - len(in_features) + 2) - assert len(mps.leaf_nodes) == leaf + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind + + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True, + mpo=mpo) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True, + mpo=mpo) + + if in_features: + assert result.shape == (100, 100) else: - leaf = (n_features + 2) + \ - (n_features - len(in_features)) - assert len(mps.leaf_nodes) == leaf - else: - if mpo_boundary == 'obc': - leaf = n_features + \ - (n_features - len(in_features) + 2) - assert len(mps.leaf_nodes) == leaf + assert result.shape == tuple() + + if mps_boundary == 'obc': + if mpo_boundary == 'obc': + leaf = (n_features + 2) + \ + (n_features - len(in_features) + 2) + assert len(mps.leaf_nodes) == leaf + else: + leaf = (n_features + 2) + \ + (n_features - len(in_features)) + assert len(mps.leaf_nodes) == leaf else: - leaf = n_features + \ - (n_features - len(in_features)) - assert len(mps.leaf_nodes) == leaf - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None - for node in mpo.mats_env: - assert node.tensor.grad is None + if mpo_boundary == 'obc': + leaf = n_features + \ + (n_features - len(in_features) + 2) + assert len(mps.leaf_nodes) == leaf + else: + leaf = n_features + \ + (n_features - len(in_features)) + assert len(mps.leaf_nodes) == leaf + + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None + for node in mpo.mats_env: + assert node.tensor.grad is None def test_all_algorithms_no_marginalize(self): for n_features in [1, 2, 3, 4, 10]: @@ -1086,42 +1121,45 @@ def test_all_algorithms_no_marginalize(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats) + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) - aux_shape = [5] * len(mps.out_features) - if in_features: - aux_shape = [100] + aux_shape - assert result.shape == tuple(aux_shape) - - if not inline_input and auto_stack: - assert len(mps.virtual_nodes) == 2 + aux_shape = [5] * len(mps.out_features) + if in_features: + aux_shape = [100] + aux_shape + assert result.shape == tuple(aux_shape) + + if not inline_input and auto_stack: + assert len(mps.virtual_nodes) == 2 + else: + assert len(mps.virtual_nodes) == 1 + else: - assert len(mps.virtual_nodes) == 1 + assert result.shape == tuple(aux_shape) + assert len(mps.virtual_nodes) == 0 - else: - assert result.shape == tuple(aux_shape) - assert len(mps.virtual_nodes) == 0 + assert len(mps.edges) == len(mps.out_features) - assert len(mps.edges) == len(mps.out_features) - - if boundary == 'obc': - assert len(mps.leaf_nodes) == n_features + 2 - else: - assert len(mps.leaf_nodes) == n_features + if boundary == 'obc': + assert len(mps.leaf_nodes) == n_features + 2 + else: + assert len(mps.leaf_nodes) == n_features + + assert len(mps.data_nodes) == len(in_features) - assert len(mps.data_nodes) == len(in_features) - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None def test_norm(self): for n_features in [1, 2, 3, 4, 10]: @@ -1264,7 +1302,7 @@ def test_partial_density_cuda(self): for node in mps.mats_env: assert node.grad is not None - def test_mutual_information(self): + def test_entropy(self): for n_features in [1, 2, 3, 4, 10]: for boundary in ['obc', 'pbc']: for middle_site in range(n_features - 1): @@ -1292,15 +1330,17 @@ def test_mutual_information(self): assert len(mps.data_nodes) == n_features # Mutual Information - scaled_mi, log_norm = mps.mi(middle_site=middle_site, - renormalize=True) - mi = mps.mi(middle_site=middle_site, - renormalize=False) + scaled_mi, log_norm = mps.entropy(middle_site=middle_site, + renormalize=True) + mi = mps.entropy(middle_site=middle_site, + renormalize=False) assert all([mps.bond_dim[i] <= bond_dim[i] for i in range(len(bond_dim))]) - assert torch.isclose(mi, log_norm.exp() * scaled_mi) + sq_norm = log_norm.exp().pow(2) + approx_mi = sq_norm * scaled_mi - sq_norm * 2 * log_norm + assert torch.isclose(mi, approx_mi) if mps.boundary == 'obc': assert len(mps.leaf_nodes) == n_features + 2 @@ -1364,7 +1404,7 @@ def test_canonicalize(self): approx_mps_tensor = mps() assert approx_mps_tensor.shape == (2,) * n_features - def test_canonicalize_same_bond_dims(self): + def test_canonicalize_diff_bond_dims(self): for n_features in [1, 2, 3, 4, 10]: for boundary in ['obc', 'pbc']: for oc in range(n_features): @@ -1561,7 +1601,8 @@ def test_initialize_with_tensors_errors(self): tensor=tensor) def test_initialize_init_method(self): - methods = ['zeros', 'ones', 'copy', 'rand', 'randn', 'randn_eye'] + methods = ['zeros', 'ones', 'copy', 'rand', 'randn', + 'randn_eye', 'unit', 'canonical'] for n in [1, 2, 5]: for init_method in methods: mps = tk.models.UMPS(n_features=n, @@ -1575,7 +1616,8 @@ def test_initialize_init_method(self): def test_initialize_init_method_cuda(self): device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') - methods = ['zeros', 'ones', 'copy', 'rand', 'randn', 'randn_eye'] + methods = ['zeros', 'ones', 'copy', 'rand', 'randn', + 'randn_eye', 'unit', 'canonical'] for n in [1, 2, 5]: for init_method in methods: mps = tk.models.UMPS(n_features=n, @@ -1715,26 +1757,29 @@ def test_all_algorithms(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats) + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) - assert result.shape == (100,) - assert len(mps.edges) == 0 - assert len(mps.leaf_nodes) == n_features - assert len(mps.data_nodes) == n_features - assert len(mps.virtual_nodes) == 2 - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None - assert mps.uniform_memory.grad is not None + assert result.shape == (100,) + assert len(mps.edges) == 0 + assert len(mps.leaf_nodes) == n_features + assert len(mps.data_nodes) == n_features + assert len(mps.virtual_nodes) == 2 + + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None + assert mps.uniform_memory.grad is not None def test_all_algorithms_marginalize(self): for n_features in [1, 2, 3, 4, 10]: @@ -1759,34 +1804,37 @@ def test_all_algorithms_marginalize(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind - - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True) - result = mps(data, - inline_input=inline_input, - inline_mats=inline_mats, - marginalize_output=True) + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind - if in_features: - assert result.shape == (100, 100) - assert len(mps.virtual_nodes) == \ - (2 + len(mps.out_features)) - else: - assert result.shape == tuple() - assert len(mps.virtual_nodes) == \ - (1 + len(mps.out_features)) - - assert len(mps.leaf_nodes) == n_features - assert len(mps.data_nodes) == len(in_features) - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None - assert mps.uniform_memory.grad is not None + mps.trace(example, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize, + marginalize_output=True) + + if in_features: + assert result.shape == (100, 100) + assert len(mps.virtual_nodes) == \ + (2 + len(mps.out_features)) + else: + assert result.shape == tuple() + assert len(mps.virtual_nodes) == \ + (1 + len(mps.out_features)) + + assert len(mps.leaf_nodes) == n_features + assert len(mps.data_nodes) == len(in_features) + + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None + assert mps.uniform_memory.grad is not None def test_all_algorithms_no_marginalize(self): for n_features in [1, 2, 3, 4, 10]: @@ -1811,33 +1859,36 @@ def test_all_algorithms_no_marginalize(self): for auto_unbind in [True, False]: for inline_input in [True, False]: for inline_mats in [True, False]: - mps.auto_stack = auto_stack - mps.auto_unbind = auto_unbind + for renormalize in [True, False]: + mps.auto_stack = auto_stack + mps.auto_unbind = auto_unbind - mps.trace(example, - inline_input=inline_input, - inline_mats=inline_mats) - result = mps(data, + mps.trace(example, inline_input=inline_input, - inline_mats=inline_mats) + inline_mats=inline_mats, + renormalize=renormalize) + result = mps(data, + inline_input=inline_input, + inline_mats=inline_mats, + renormalize=renormalize) - aux_shape = [5] * len(mps.out_features) - if in_features: - aux_shape = [100] + aux_shape - assert result.shape == tuple(aux_shape) - assert len(mps.virtual_nodes) == 2 - else: - assert result.shape == tuple(aux_shape) - assert len(mps.virtual_nodes) == 1 + aux_shape = [5] * len(mps.out_features) + if in_features: + aux_shape = [100] + aux_shape + assert result.shape == tuple(aux_shape) + assert len(mps.virtual_nodes) == 2 + else: + assert result.shape == tuple(aux_shape) + assert len(mps.virtual_nodes) == 1 + + assert len(mps.edges) == len(mps.out_features) + assert len(mps.leaf_nodes) == n_features + assert len(mps.data_nodes) == len(in_features) - assert len(mps.edges) == len(mps.out_features) - assert len(mps.leaf_nodes) == n_features - assert len(mps.data_nodes) == len(in_features) - - result.sum().backward() - for node in mps.mats_env: - assert node.grad is not None - assert mps.uniform_memory.grad is not None + result.sum().backward() + for node in mps.mats_env: + assert node.grad is not None + assert mps.uniform_memory.grad is not None def test_norm(self): for n_features in [1, 2, 3, 4, 10]: @@ -2029,7 +2080,8 @@ def test_initialize_with_tensors_ignore_rest(self): assert mps.bond_dim == [10] * 10 def test_initialize_init_method(self): - methods = ['zeros', 'ones', 'copy', 'rand', 'randn', 'randn_eye'] + methods = ['zeros', 'ones', 'copy', 'rand', 'randn', + 'randn_eye', 'unit', 'canonical'] for n in [1, 2, 5]: for init_method in methods: # PBC @@ -2070,7 +2122,8 @@ def test_initialize_init_method(self): def test_initialize_init_method_cuda(self): device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') - methods = ['zeros', 'ones', 'copy', 'rand', 'randn', 'randn_eye'] + methods = ['zeros', 'ones', 'copy', 'rand', 'randn', + 'randn_eye', 'unit', 'canonical'] for n in [1, 2, 5]: for init_method in methods: # PBC @@ -2115,6 +2168,81 @@ def test_initialize_init_method_cuda(self): assert torch.equal(mps.right_node.tensor[1:], torch.zeros_like(mps.right_node.tensor)[1:]) + def test_initialize_canonical(self): + for n in [1, 2, 5]: + # PBC + mps = tk.models.MPSLayer(boundary='pbc', + n_features=n, + in_dim=10, + out_dim=10, + bond_dim=10, + init_method='canonical') + assert mps.n_features == n + assert mps.boundary == 'pbc' + assert mps.phys_dim == [10] * n + assert mps.bond_dim == [10] * n + + # For PBC norm does not have to be 10**n always + + # OBC + mps = tk.models.MPSLayer(boundary='obc', + n_features=n, + in_dim=10, + out_dim=10, + bond_dim=10, + init_method='canonical') + assert mps.n_features == n + assert mps.boundary == 'obc' + assert mps.phys_dim == [10] * n + assert mps.bond_dim == [10] * (n - 1) + + # Check it has norm == 10**n + norm = mps.norm() + assert mps.norm().isclose(torch.tensor(10. ** n).sqrt()) + # Norm is close to 10**n if bond dimension is <= than + # physical dimension, otherwise, it will not be exactly 10**n + + def test_initialize_canonical_cuda(self): + device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') + for n in [1, 2, 5]: + # PBC + mps = tk.models.MPSLayer(boundary='pbc', + n_features=n, + in_dim=10, + out_dim=10, + bond_dim=10, + init_method='canonical', + device=device) + assert mps.n_features == n + assert mps.boundary == 'pbc' + assert mps.phys_dim == [10] * n + assert mps.bond_dim == [10] * n + for node in mps.mats_env: + assert node.device == device + + # For PBC norm does not have to be 10**n always + + # OBC + mps = tk.models.MPSLayer(boundary='obc', + n_features=n, + in_dim=10, + out_dim=10, + bond_dim=10, + init_method='canonical', + device=device) + assert mps.n_features == n + assert mps.boundary == 'obc' + assert mps.phys_dim == [10] * n + assert mps.bond_dim == [10] * (n - 1) + for node in mps.mats_env: + assert node.device == device + + # Check it has norm == 10**n + norm = mps.norm() + assert mps.norm().isclose(torch.tensor(10. ** n).sqrt()) + # Norm is close to 10**n if bond dimension is <= than + # physical dimension, otherwise, it will not be exactly 10**n + def test_in_and_out_features(self): tensors = [torch.randn(10, 2, 10) for _ in range(10)] mps = tk.models.MPSLayer(tensors=tensors, @@ -2253,9 +2381,11 @@ def test_initialize_with_tensors(self): assert mps.out_node.tensor is not mps.uniform_memory.tensor def test_initialize_init_method(self): - methods = ['zeros', 'ones', 'copy', 'rand', 'randn', 'randn_eye'] + methods = ['zeros', 'ones', 'copy', 'rand', 'randn', + 'randn_eye', 'unit', 'canonical'] for n in [1, 2, 5]: for init_method in methods: + print(init_method) mps = tk.models.UMPSLayer(n_features=n, in_dim=2, out_dim=5, diff --git a/tests/test_operations.py b/tests/test_operations.py index 0524749..9d51a49 100644 --- a/tests/test_operations.py +++ b/tests/test_operations.py @@ -259,6 +259,97 @@ def test_mul(self, setup): # Tensor product cannot be performed between connected nodes with pytest.raises(ValueError): node3 = node1 % node2 + + def test_mul_with_tensor(self, setup): + node1, _ = setup + + assert node1.successors == dict() + + node2 = node1 * torch.randn(node1.shape) + assert node2.shape == (2, 3) + assert node2.edges == node1.edges + assert node1.successors != dict() + assert node1.successors['mul'][(node1,)].child == node2 + + # Second time (with maybe different tensor) + node3 = node1 * torch.randn(node1.shape) + assert node2 is node3 + + # Third time (with maybe another type of operand) + node4 = node1 * 5. + assert node2 is node4 + + # Operating in the opposite order raises an error + with pytest.raises(TypeError): + _ = 3. * node1 + + def test_div(self, setup): + node1, node2 = setup + node1[0].change_size(2) + node1[1].change_size(2) + node2[0].change_size(2) + node2[1].change_size(2) + + # Tensor product cannot be performed between nodes in different networks + with pytest.raises(ValueError): + node3 = node1 / node2 + + net = tk.TensorNetwork() + node1.network = net + node2.network = net + + assert node1.successors == dict() + assert node2.successors == dict() + + node3 = node1 / node2 + assert node3.shape == (2, 2) + assert node3.edges == node1.edges + assert node1.successors != dict() + assert node1.successors['div'][(node1, node2)].child == node3 + assert node2.successors == dict() + + result_tensor = node3.tensor + + # Second time + node3 = node1 / node2 + assert torch.equal(result_tensor, node3.tensor) + + assert len(net.nodes) == 3 + assert len(net.leaf_nodes) == 2 + assert len(net.resultant_nodes) == 1 + + net.reset() + assert len(net.nodes) == 2 + assert len(net.leaf_nodes) == 2 + assert len(net.resultant_nodes) == 0 + + node1[1] ^ node2[0] + # Tensor product cannot be performed between connected nodes + with pytest.raises(ValueError): + node3 = node1 % node2 + + def test_div_with_tensor(self, setup): + node1, _ = setup + + assert node1.successors == dict() + + node2 = node1 / torch.randn(node1.shape) + assert node2.shape == (2, 3) + assert node2.edges == node1.edges + assert node1.successors != dict() + assert node1.successors['div'][(node1,)].child == node2 + + # Second time (with maybe different tensor) + node3 = node1 / torch.randn(node1.shape) + assert node2 is node3 + + # Third time (with maybe another type of operand) + node4 = node1 / 5. + assert node2 is node4 + + # Operating in the opposite order raises an error + with pytest.raises(TypeError): + _ = 3. * node1 def test_add(self, setup): node1, node2 = setup @@ -304,6 +395,29 @@ def test_add(self, setup): # Tensor product cannot be performed between connected nodes with pytest.raises(ValueError): node3 = node1 % node2 + + def test_add_with_tensor(self, setup): + node1, _ = setup + + assert node1.successors == dict() + + node2 = node1 + torch.randn(node1.shape) + assert node2.shape == (2, 3) + assert node2.edges == node1.edges + assert node1.successors != dict() + assert node1.successors['add'][(node1,)].child == node2 + + # Second time (with maybe different tensor) + node3 = node1 + torch.randn(node1.shape) + assert node2 is node3 + + # Third time (with maybe another type of operand) + node4 = node1 + 5. + assert node2 is node4 + + # Operating in the opposite order raises an error + with pytest.raises(TypeError): + _ = 3. * node1 def test_sub(self, setup): node1, node2 = setup @@ -349,6 +463,64 @@ def test_sub(self, setup): # Tensor product cannot be performed between connected nodes with pytest.raises(ValueError): node3 = node1 % node2 + + def test_sub_with_tensor(self, setup): + node1, _ = setup + + assert node1.successors == dict() + + node2 = node1 - torch.randn(node1.shape) + assert node2.shape == (2, 3) + assert node2.edges == node1.edges + assert node1.successors != dict() + assert node1.successors['sub'][(node1,)].child == node2 + + # Second time (with maybe different tensor) + node3 = node1 - torch.randn(node1.shape) + assert node2 is node3 + + # Third time (with maybe another type of operand) + node4 = node1 - 5. + assert node2 is node4 + + # Operating in the opposite order raises an error + with pytest.raises(TypeError): + _ = 3. * node1 + + def test_renormalize(self): + node1 = tk.randn(shape=(3, 3, 3), + axes_names=('left', 'input', 'right')) + assert node1.successors == dict() + + node2 = tk.renormalize(node1) + assert node2 is not node1 + assert node2.shape == (3, 3, 3) + assert node2.tensor.norm().isclose(torch.tensor(1.)) + assert node1.successors != dict() + + # Repeat + node2 = tk.renormalize(node1) + + node3 = node1.renormalize(p=1, axis='left') + assert node3 is not node1 + assert node3 is not node2 + assert node3.shape == (3, 3, 3) + assert node3.tensor.norm(p=1, dim=0).allclose(torch.ones(3, 3)) + assert node1.successors != dict() + + # Repeat + node3 = node1.renormalize(p=1, axis='left') + + node4 = node1.renormalize(axis=['left', 'right']) + assert node4 is not node1 + assert node4 is not node2 + assert node4 is not node3 + assert node4.shape == (3, 3, 3) + assert node4.tensor.norm(dim=[0, 2]).allclose(torch.ones(3)) + assert node1.successors != dict() + + # Repeat + node4 = node1.renormalize(axis=['left', 'right']) @pytest.fixture def setup_param(self): @@ -464,6 +636,97 @@ def test_mul_param(self, setup_param): # Tensor product cannot be performed between connected nodes with pytest.raises(ValueError): node3 = node1 % node2 + + def test_mul_with_tensor_param(self, setup_param): + node1, _ = setup_param + + assert node1.successors == dict() + + node2 = node1 * torch.randn(node1.shape) + assert node2.shape == (2, 3) + assert node2.edges == node1.edges + assert node1.successors != dict() + assert node1.successors['mul'][(node1,)].child == node2 + + # Second time (with maybe different tensor) + node3 = node1 * torch.randn(node1.shape) + assert node2 is node3 + + # Third time (with maybe another type of operand) + node4 = node1 * 5. + assert node2 is node4 + + # Operating in the opposite order raises an error + with pytest.raises(TypeError): + _ = 3. * node1 + + def test_div_param(self, setup_param): + node1, node2 = setup_param + node1[0].change_size(2) + node1[1].change_size(2) + node2[0].change_size(2) + node2[1].change_size(2) + + # Tensor product cannot be performed between nodes in different networks + with pytest.raises(ValueError): + node3 = node1 / node2 + + net = tk.TensorNetwork() + node1.network = net + node2.network = net + + assert node1.successors == dict() + assert node2.successors == dict() + + node3 = node1 / node2 + assert node3.shape == (2, 2) + assert node3.edges == node1.edges + assert node1.successors != dict() + assert node1.successors['div'][(node1, node2)].child == node3 + assert node2.successors == dict() + + result_tensor = node3.tensor + + # Second time + node3 = node1 / node2 + assert torch.equal(result_tensor, node3.tensor) + + assert len(net.nodes) == 3 + assert len(net.leaf_nodes) == 2 + assert len(net.resultant_nodes) == 1 + + net.reset() + assert len(net.nodes) == 2 + assert len(net.leaf_nodes) == 2 + assert len(net.resultant_nodes) == 0 + + node1[1] ^ node2[0] + # Tensor product cannot be performed between connected nodes + with pytest.raises(ValueError): + node3 = node1 % node2 + + def test_div_with_tensor_param(self, setup_param): + node1, _ = setup_param + + assert node1.successors == dict() + + node2 = node1 / torch.randn(node1.shape) + assert node2.shape == (2, 3) + assert node2.edges == node1.edges + assert node1.successors != dict() + assert node1.successors['div'][(node1,)].child == node2 + + # Second time (with maybe different tensor) + node3 = node1 / torch.randn(node1.shape) + assert node2 is node3 + + # Third time (with maybe another type of operand) + node4 = node1 / 5. + assert node2 is node4 + + # Operating in the opposite order raises an error + with pytest.raises(TypeError): + _ = 3. * node1 def test_add_param(self, setup_param): node1, node2 = setup_param @@ -517,6 +780,29 @@ def test_add_param(self, setup_param): # Tensor product cannot be performed between connected nodes with pytest.raises(ValueError): node3 = node1 % node2 + + def test_add_with_tensor_param(self, setup_param): + node1, _ = setup_param + + assert node1.successors == dict() + + node2 = node1 + torch.randn(node1.shape) + assert node2.shape == (2, 3) + assert node2.edges == node1.edges + assert node1.successors != dict() + assert node1.successors['add'][(node1,)].child == node2 + + # Second time (with maybe different tensor) + node3 = node1 + torch.randn(node1.shape) + assert node2 is node3 + + # Third time (with maybe another type of operand) + node4 = node1 + 5. + assert node2 is node4 + + # Operating in the opposite order raises an error + with pytest.raises(TypeError): + _ = 3. * node1 def test_sub_param(self, setup_param): node1, node2 = setup_param @@ -570,6 +856,65 @@ def test_sub_param(self, setup_param): # Tensor product cannot be performed between connected nodes with pytest.raises(ValueError): node3 = node1 % node2 + + def test_sub_with_tensor_param(self, setup_param): + node1, _ = setup_param + + assert node1.successors == dict() + + node2 = node1 - torch.randn(node1.shape) + assert node2.shape == (2, 3) + assert node2.edges == node1.edges + assert node1.successors != dict() + assert node1.successors['sub'][(node1,)].child == node2 + + # Second time (with maybe different tensor) + node3 = node1 - torch.randn(node1.shape) + assert node2 is node3 + + # Third time (with maybe another type of operand) + node4 = node1 - 5. + assert node2 is node4 + + # Operating in the opposite order raises an error + with pytest.raises(TypeError): + _ = 3. * node1 + + def test_renormalize_param(self): + node1 = tk.randn(shape=(3, 3, 3), + axes_names=('left', 'input', 'right'), + param_node=True) + assert node1.successors == dict() + + node2 = tk.renormalize(node1) + assert node2 is not node1 + assert node2.shape == (3, 3, 3) + assert node2.tensor.norm().isclose(torch.tensor(1.)) + assert node1.successors != dict() + + # Repeat + node2 = tk.renormalize(node1) + + node3 = node1.renormalize(p=1, axis='left') + assert node3 is not node1 + assert node3 is not node2 + assert node3.shape == (3, 3, 3) + assert node3.tensor.norm(p=1, dim=0).allclose(torch.ones(3, 3)) + assert node1.successors != dict() + + # Repeat + node3 = node1.renormalize(p=1, axis='left') + + node4 = node1.renormalize(axis=['left', 'right']) + assert node4 is not node1 + assert node4 is not node2 + assert node4 is not node3 + assert node4.shape == (3, 3, 3) + assert node4.tensor.norm(dim=[0, 2]).allclose(torch.ones(3)) + assert node1.successors != dict() + + # Repeat + node4 = node1.renormalize(axis=['left', 'right']) class TestSplitSVD: